Programming

RequireJS에서 단위 테스트에 대한 종속성을 어떻게 조롱 할 수 있습니까?

procodes 2020. 7. 6. 21:29
반응형

RequireJS에서 단위 테스트에 대한 종속성을 어떻게 조롱 할 수 있습니까?


테스트하려는 AMD 모듈이 있지만 실제 종속성을로드하는 대신 해당 종속성을 모방하려고합니다. requirejs를 사용하고 있으며 모듈 코드는 다음과 같습니다.

define(['hurp', 'durp'], function(Hurp, Durp) {
  return {
    foo: function () {
      console.log(Hurp.beans)
    },
    bar: function () {
      console.log(Durp.beans)
    }
  }
}

어떻게 밖으로 조롱 수 hurpdurp내가 단위 테스트를 효과적으로 할 수 있도록?


따라서이 게시물 을 읽은 후 requirejs 구성 기능을 사용하여 테스트를위한 새로운 컨텍스트를 작성하여 종속성을 조롱 할 수있는 솔루션을 찾았습니다.

var cnt = 0;
function createContext(stubs) {
  cnt++;
  var map = {};

  var i18n = stubs.i18n;
  stubs.i18n = {
    load: sinon.spy(function(name, req, onLoad) {
      onLoad(i18n);
    })
  };

  _.each(stubs, function(value, key) {
    var stubName = 'stub' + key + cnt;

    map[key] = stubName;

    define(stubName, function() {
      return value;
    });
  });

  return require.config({
    context: "context_" + cnt,
    map: {
      "*": map
    },
    baseUrl: 'js/cfe/app/'
  });
}

그것은에 대한 정의 새로운 컨텍스트 생성 그래서 Hurp와는 Durp당신이 함수에 전달 된 객체에 의해 설정됩니다. 이름에 대한 Math.random은 약간 더러울 수 있지만 작동합니다. 많은 테스트를 해야하는 경우 모의 재사용을 방지하거나 실제 requirejs 모듈을 원할 때 모의 객체를로드하기 위해 모든 스위트에 대해 새로운 컨텍스트를 만들어야합니다.

귀하의 경우 다음과 같이 보일 것입니다 :

(function () {

  var stubs =  {
    hurp: 'hurp',
    durp: 'durp'
  };
  var context = createContext(stubs);

  context(['yourModuleName'], function (yourModule) {

    //your normal jasmine test starts here

    describe("yourModuleName", function () {
      it('should log', function(){
         spyOn(console, 'log');
         yourModule.foo();

         expect(console.log).toHasBeenCalledWith('hurp');
      })
    });
  });
})();

그래서 나는 생산 에서이 접근법을 잠시 동안 사용하고 있으며 실제로 강력합니다.


새로운 Squire.js 라이브러리 를 확인하고 싶을 수도 있습니다.

문서에서 :

Squire.js는 Require.js 사용자가 모의 종속성을 쉽게 만들 수있는 종속성 인젝터입니다!


나는이 문제에 대한 세 가지 다른 해결책을 찾았습니다.

인라인 종속성 정의

define('hurp', [], function () {
  return {
    beans: 'Beans'
  };
});

define('durp', [], function () {
  return {
    beans: 'durp beans'
  };
});

require('hurpdhurp', function () {
  // test hurpdurp in here
});

못 생겼어 많은 AMD 상용구로 테스트를 복잡하게 만들어야합니다.

다른 경로에서 모의 ​​종속성로드

여기에는 별도의 config.js 파일을 사용하여 원래 종속성 대신 모의 객체를 가리키는 각 종속성의 경로를 정의해야합니다. 또한 테스트 파일과 구성 파일을 생성해야하는 경우도 있습니다.

노드에서 가짜

이것은 현재의 솔루션이지만 여전히 끔찍한 솔루션입니다.

define모듈에 고유 한 모의를 제공하고 테스트를 콜백에 넣는 고유 한 함수를 만듭니다 . 그런 다음 eval테스트를 실행하는 모듈은 다음 과 같습니다.

var fs = require('fs')
  , hurp = {
      beans: 'BEANS'
    }
  , durp = {
      beans: 'durp beans'
    }
  , hurpDurp = fs.readFileSync('path/to/hurpDurp', 'utf8');
  ;



function define(deps, cb) {
  var TestableHurpDurp = cb(hurp, durp);
  // now run tests below on TestableHurpDurp, which is using your
  // passed-in mocks as dependencies.
}

// evaluate the AMD module, running your mocked define function and your tests.
eval(hurpDurp);

이것이 내가 선호하는 솔루션입니다. 약간의 마술처럼 보이지만 몇 가지 이점이 있습니다.

  1. 브라우저 자동화를 방해하지 않고 노드에서 테스트를 실행하십시오.
  2. 테스트에서 지저분한 AMD 상용구가 필요 없습니다.
  3. 당신은 사용할 수 eval분노하고, 크록 포드가 분노로 폭발하는 상상.

분명히 여전히 몇 가지 단점이 있습니다.

  1. 노드에서 테스트 중이므로 브라우저 이벤트 또는 DOM 조작으로는 아무것도 할 수 없습니다. 로직 테스트에만 적합합니다.
  2. 아직 설정하기가 약간 어색합니다. define테스트가 실제로 실행되는 곳이기 때문에 모든 테스트에서 조롱해야 합니다.

이런 종류의 것들에 대해 더 좋은 구문을 제공하기 위해 테스트 러너에서 일하고 있지만 여전히 문제 1에 대한 좋은 해결책이 없습니다.

결론

requirejs에서 조롱하는 사람들은 열심히 짜증납니다. 나는 일종의 작동 방식을 찾았지만 여전히 그것에 만족하지 않습니다. 더 좋은 아이디어가 있으면 알려주세요.


있다 config.map옵션 http://requirejs.org/docs/api.html#config-map은 .

사용 방법 :

  1. 일반 모듈을 정의하십시오.
  2. 스텁 모듈을 정의하십시오.
  3. RequireJS를 신속하게 구성하십시오.

    requirejs.config({
      map: {
        'source/js': {
          'foo': 'normalModule'
        },
        'source/test': {
          'foo': 'stubModule'
        }
      }
    });
    

In this case for normal and test code you could use the foo module which will be real module reference and stub accordingly.


You can use testr.js to mock dependencies. You can set testr to load the mock dependencies instead of the original ones. Here is an example usage:

var fakeDep = function(){
    this.getText = function(){
        return 'Fake Dependancy';
    };
};

var Module1 = testr('module1', {
    'dependancies/dependancy1':fakeDep
});

Check out this as well: http://cyberasylum.janithw.com/mocking-requirejs-dependencies-for-unit-testing/


This answer is based on Andreas Köberle's answer.
It wasn't that easy for me to implement and understand his solution, so I'll explain it in a bit more detail how it works, and some pitfalls to avoid, hoping that it will help future visitors.

So, first of all the setup:
I'm using Karma as test runner and MochaJs as test framework.

Using something like Squire didn't work for me, for some reason, when I used it, the test framework threw errors:

TypeError: Cannot read property 'call' of undefined

RequireJs has the possibility to map module ids to other module ids. It also allows to create a require function that uses a different config than the global require.
These features is crucial for this solution to work.

Here is my version of the mock code, including (a lot) comments (I hope its understandable). I wrapped it inside a module, so that the tests can easily require it.

define([], function () {
    var count = 0;
    var requireJsMock= Object.create(null);
    requireJsMock.createMockRequire = function (mocks) {
        //mocks is an object with the module ids/paths as keys, and the module as value
        count++;
        var map = {};

        //register the mocks with unique names, and create a mapping from the mocked module id to the mock module id
        //this will cause RequireJs to load the mock module instead of the real one
        for (property in mocks) {
            if (mocks.hasOwnProperty(property)) {
                var moduleId = property;  //the object property is the module id
                var module = mocks[property];   //the value is the mock
                var stubId = 'stub' + moduleId + count;   //create a unique name to register the module

                map[moduleId] = stubId;   //add to the mapping

                //register the mock with the unique id, so that RequireJs can actually call it
                define(stubId, function () {
                    return module;
                });
            }
        }

        var defaultContext = requirejs.s.contexts._.config;
        var requireMockContext = { baseUrl: defaultContext.baseUrl };   //use the baseUrl of the global RequireJs config, so that it doesn't have to be repeated here
        requireMockContext.context = "context_" + count;    //use a unique context name, so that the configs dont overlap
        //use the mapping for all modules
        requireMockContext.map = {
            "*": map
        };
        return require.config(requireMockContext);  //create a require function that uses the new config
    };

    return requireJsMock;
});

The biggest pitfall I encountered, which literally cost me hours, was creating the RequireJs config. I tried to (deep) copy it, and only override the necessary properties (like context or map). This does not work! Only copy the baseUrl, this works fine.

Usage

To use it, require it in your test, create the mocks, and then pass it to createMockRequire. For example:

var ModuleMock = function () {
    this.method = function () {
        methodCalled += 1;
    };
};
var mocks = {
    "ModuleIdOrPath": ModuleMock
}
var requireMocks = mocker.createMockRequire(mocks);

And here an example of a complete test file:

define(["chai", "requireJsMock"], function (chai, requireJsMock) {
    var expect = chai.expect;

    describe("Module", function () {
        describe("Method", function () {
            it("should work", function () {
                return new Promise(function (resolve, reject) {
                    var handler = { handle: function () { } };

                    var called = 0;
                    var moduleBMock = function () {
                        this.method = function () {
                            methodCalled += 1;
                        };
                    };
                    var mocks = {
                        "ModuleBIdOrPath": moduleBMock
                    }
                    var requireMocks = requireJsMock.createMockRequire(mocks);

                    requireMocks(["js/ModuleA"], function (moduleA) {
                        try {
                            moduleA.method();   //moduleA should call method of moduleBMock
                            expect(called).to.equal(1);
                            resolve();
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
            });
        });
    });
});

if you want to make some plain js tests which isolate one unit, then you can simply use this snippet:

function define(args, func){
    if(!args.length){
        throw new Error("please stick to the require.js api which wants a: define(['mydependency'], function(){})");
    }

    var fileName = document.scripts[document.scripts.length-1].src;

    // get rid of the url and path elements
    fileName = fileName.split("/");
    fileName = fileName[fileName.length-1];

    // get rid of the file ending
    fileName = fileName.split(".");
    fileName = fileName[0];

    window[fileName] = func;
    return func;
}
window.define = define;

참고URL : https://stackoverflow.com/questions/11439540/how-can-i-mock-dependencies-for-unit-testing-in-requirejs

반응형