简体   繁体   English

Node.js模块单元测试-使用sinon.js暂存异步文件系统调用

[英]Node.js module unit testing - stubbing asynchronous file system calls with sinon.js

I've been struggling with this now for several hours and I'm not getting anywhere. 我已经为此苦苦挣扎了好几个小时,而且我什么都没走。 This is a simplified version of the code I'm working with: 这是我正在使用的代码的简化版本:

I have a module called 'setup' that internally reads files from the filesystem, and does some stuff. 我有一个名为“ setup”的模块,该模块在内部从文件系统读取文件,并执行一些操作。 I'm attempting to write some unit tests that use stubs to return fakedata rather than read from the file system. 我试图编写一些使用存根返回假数据而不是从文件系统读取的单元测试。 I'm using the sandbox feature of nodeunit since the functions that I am stubbing are not exported. 我正在使用nodeunit的沙盒功能,因为我正在存根的功能不会导出。

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

index.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: loader.js:

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

data.js: data.js:

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

test-setup.js: test-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();
  }

});

Test 1 passes. 测试1通过。 Test 2 fails with: AssertionError: returned value is the fakedata from the stub 测试2失败并显示:AssertionError:返回的值是存根中的fakedata

The log messages show that it has printed the data from the file rather than the fakedata. 日志消息显示它已打印文件中的数据,而不是假数据。 When I inspect the this.sandbox.loader function in the debugger it is correctly set to the stub. 当我在调试器中检查this.sandbox.loader函数时,它已正确设置为存根。

The second issue is that I had to fake out the require and pass in the loader function with: 第二个问题是,我不得不伪造require并通过以下方式传入loader函数:

require : function () { return loader; require:function(){返回加载器; }, },

Otherwise require couldn't find the ./loader. 否则要求找不到./loader。 I tried to change directory with process.chdir before the test, but the require still failed even when the cwd was correctly set to the project directory. 我尝试在测试之前使用process.chdir更改目录,但是即使cwd正确设置为项目目录,require仍然失败。 Obviously faking out require would only work if there is 1 module required, in the real code there are several requires. 显然,伪造require仅在需要1个模块的情况下有效,在实际代码中有多个require。 The only other way I got the ./loader to load was to modify the code to use an absolute path, and editing the code each time to run the test isn't feasible. 我加载./loader的唯一另一种方法是修改代码以使用绝对路径,并且每次运行测试都编辑代码是不可行的。

I've probably missed something obvious, writing unit tests for module code that loads data asynchronously must be pretty standard stuff. 我可能已经错过了一些显而易见的事情,为异步加载数据的模块代码编写单元测试必须是非常标准的事情。 Is sandbox/stubbing the right approach? 沙箱/存根是正确的方法吗?

Any help much appreciated. 任何帮助,不胜感激。

After quite a lot of insanity I came up with a solution using proxyquire which "Proxies nodejs require in order to allow overriding dependencies during testing". 经过相当多的理智之后 ,我想出了一个使用proxyquire的解决方案,该解决方案“代理nodejs以便在测试期间覆盖依赖项”。 Much better than using a nodeunit sandbox. 比使用nodeunit沙箱要好得多。

I also had to update the module to accept a callback function, and modify loader.js to export the function as 'load'. 我还必须更新模块以接受回调函数,并修改loader.js以将函数导出为“ load”。

Hopefully this will be useful to someone out there. 希望这对外面的人有用。

index.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: loader.js:

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

test-setup.js: test-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();
  });
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM