Модульное тестирование модуля Node.js - блокирование вызовов асинхронной файловой системы с помощью sinon.js

Я боролся с этим уже несколько часов и никуда не денусь. Это упрощенная версия кода, с которым я работаю:

У меня есть модуль под названием "setup", который внутренне читает файлы из файловой системы и выполняет некоторые вещи. Я пытаюсь написать некоторые модульные тесты, которые используют заглушки для возврата fakedata, а не для чтения из файловой системы. Я использую функцию песочницы в nodeunit, так как функции, которые я создаю, не экспортируются.

setup
├── data.js
├── index.js
├── loader.js
├── package.json
└── test
    └── test-setup.js

index.js:

var
  loader = require( './loader' );

module.exports = function () {
  // do stuff
  loader( function ( data ) {
    console.log( "read from file: " + JSON.stringify( data, null, 2 ));
    // do more stuff
    return data;
  });
};

loader.js:

module.exports = function ( cb ) {
  var data = require( './data' );
  cb( data );
};

data.js:

module.exports = {
  data: {
    field1: "value1",
    field2: "value2"
  }
};

тест-setup.js:

var
  nodeunit = require( 'nodeunit' ),
  sandbox  = require( 'nodeunit' ).utils.sandbox,
  sinon    = require( 'sinon' ),
  loader   = require( '../loader' ),
  setup    = require( '..' );

exports[ 'setup file loading' ] = nodeunit.testCase ({
  setUp: function ( callback ) {
    var
      boxModule = { exports: {} },
      boxGlobals = {
        module  : boxModule,
        exports : boxModule.exports,
        loader  : loader,
        require : function () { return loader; },
        console : console
      };
    this.fakedata = {
      fakedata: {
        field1: "value1",
        field2: "value2"
      }
    };
    this.sandbox = sandbox( '../index.js', boxGlobals );
    callback();
  },

  tearDown: function ( callback ) {
    delete this.sandbox;
    callback();
  },

  'test1: setup by loading the file normally': function ( t ) {
    t.ok( setup() === undefined, 'data is undefined - has not loaded yet' );
    t.done();
  },

  'test2: setup using sandbox and loader function replaced with stub': function ( t ) {
    var fakedata = this.fakedata;
    var returnFakeData = function () {
      return fakedata;
    };
    var stub = sinon.stub( this.sandbox, 'loader', returnFakeData);
    t.equal( this.sandbox.module.exports(), this.fakedata, 'returned value is the fakedata from the stub' );
    t.done();
  }

});

Тест 1 проходит. Тест 2 завершается неудачно с: AssertionError: возвращаемое значение является фальшивыми данными из заглушки

Сообщения журнала показывают, что он напечатал данные из файла, а не фальшивые данные. Когда я проверяю функцию this.sandbox.loader в отладчике, она правильно установлена ​​на заглушку.

Вторая проблема заключается в том, что мне пришлось подделать require и передать в функцию загрузчика:

require: function () {return loader; },

В противном случае require не может найти./loader. Я попытался изменить каталог с помощью process.chdir перед тестом, но требование все равно не удалось, даже если cwd был правильно установлен в каталог проекта. Очевидно, что фиктивное требование будет работать, только если требуется 1 модуль, в реальном коде есть несколько требований. Единственный другой способ загрузить загрузчик./loader - это изменить код так, чтобы он использовал абсолютный путь, и редактировать код каждый раз для запуска теста невозможно.

Я, вероятно, упустил что-то очевидное, написание модульных тестов для кода модуля, который загружает данные асинхронно, должно быть довольно стандартным. Является ли песочница / заглушка правильным подходом?

Любая помощь высоко ценится.

1 ответ

Решение

После большого безумия я придумал решение, использующее proxyquire, которое "Projies nodejs требует, чтобы разрешить переопределение зависимостей во время тестирования". Гораздо лучше, чем использовать песочницу для узла узла.

Мне также пришлось обновить модуль для принятия функции обратного вызова и изменить файл loader.js, чтобы экспортировать функцию как "загрузка".

Надеюсь, это будет полезно для кого-то там.

index.js:

var
  loader = require( './loader' );

module.exports = function ( file, cb ) {
  // do stuff
  loader.load( file, function ( data ) {
    console.log( "read from file: " + JSON.stringify( data, null, 2 ));
    // do more stuff
    cb ( data );
  });
};

loader.js:

module.exports = {
  load: function ( file, cb ) {
    var data = require( file );
    cb( data );
  }
};

тест-setup.js:

var
  proxyquire = require( 'proxyquire' ),
  sinon      = require( 'sinon' ),
  pathStub   = {},
  fakedata   = {
    fakedata: {
      field1: "fakevalue1",
      field2: "fakevalue2"
    }
  },
  setup = proxyquire('..', {
    './loader': pathStub
  });

exports.testSetup = function ( t ) {
  pathStub.load = sinon.stub();
  pathStub.load.yields( fakedata );

  t.expect( 1 );
  setup( './data', function ( data ) {
    t.equal( data, fakedata, 'setup returned fakedata' );
    t.done();
  });
};
Другие вопросы по тегам