mattfysh / testr.js

Unit testing require.js modules, with both stubbed and script-loaded dependencies. Compatible with all test frameworks - Jasmine, QUnit, JsTestDriver, Buster JS, etc.
159 stars 7 forks source link

Testr fails with define #12

Closed rikkertkoppes closed 11 years ago

rikkertkoppes commented 11 years ago

This works as expected:

require(['module1'], function(){
    console.log('module loaded');
    var fakeDep = function(){
        this.getText = function(){
            return 'Fake Dependancy';
        };
    };

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

    var m1 = new Module1();
    m1.run();
});

This doesn't:

define('test',['module1'], function(){
    console.log('module loaded');
    var fakeDep = function(){
        this.getText = function(){
            return 'Fake Dependancy';
        };
    };

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

    var m1 = new Module1();
    m1.run();
});

require(['test'],function() {
    console.log('test loaded');
});

In fact, when testr is included as a library, the module doesn't even gets loaded at all(no log).

My current use case: i use a single require() for the test suite, which depends in lots of separate test specs (in jasmine). Hence all test specs are in define()'s instead of require()'s

mattfysh commented 11 years ago

This is a strange edge case, I'm unsure how using the define function for your test suites is beneficial, however if you still want to use this method - you will have to use testr.restore() after loading your source modules, and before loading your test modules. This will prevent testr from intercepting define and require and pass these calls directly to RequireJS.

rikkertkoppes commented 11 years ago

That doesn't quite work. The way components are loaded in my case is:

Basically: require - define - define. That last define is the one that testr should handle. All the other ones should be normal.

I got it kinda working by not initializing testr at all and calling the initializer just before the define in the code under test, like so:

[main.js]
require(['spec'],function() {
    //run tests
    window.__testacular__.start();
});

[spec.js]
define(['code'],function() {
    describe('a test',function() {
        it('should pass',function() {
            expect(true).toBe(true);
        });
    });
});

[code.js]
initTestr();
define(['dependencyToBeMocked'],function() {
    //stuff
});

However, that would force me to change to be tested code, which is not something that should be needed.

mattfysh commented 11 years ago

testr.js works by capturing all define calls, and storing a reference to all of the functions that define each module. The functions are not executed immediately (which is why you couldn't see the console.log earlier), but treated like factories which can be used to create new instances of that module (to avoid test context pollution).

So load up your code as usual, allow testr.js to capture all of the AMD functions, and then trigger these test suite functions by calling testr('moduleName'). You don't need to save the return value if you only want to execute the function for that module.

[main.js]
require(['spec'],function() {
    //make testr.js execute the AMD functions
    testr('spec'); // this will result in the console.log in spec.js, below
    //run tests
    window.__testacular__.start();
});

[spec.js]
define(['code'],function() {
    console.log('setting up tests...');
    describe('a test',function() {
        it('should pass',function() {
            expect(true).toBe(true);
        });
    });
});

[code.js]
define(['dependencyToBeMocked'],function() {
    //stuff
});
rikkertkoppes commented 11 years ago

Thanks a lot, got this working. For reference, I got this setup

In the testacular configuration:

files = [
  JASMINE,
  JASMINE_ADAPTER,
  REQUIRE,
  REQUIRE_ADAPTER,

  'test/helpers/testr.js',
  'test/helpers/testUtil.js',
  'test/helpers/matchers.js',
  'test/spec/main.js',
  {pattern: 'src/**/*.js', included: false},
  {pattern: 'test/spec/*Spec.js', included: false}
];

the main.js loads all tests and starts off testacular:

define('suite',[
    './test/spec/SomeSpec',
    './test/spec/SomeOtherSpec'
],function() {});

require(['suite'], function(suite) {
    testr('suite');
    window.__testacular__.start();
});

every spec has its own dependecies (SomeSpec.js) and uses testr to Mock dependencies if needed:

define([
    'someModuleToBeTested'
],function(Module) {
    describe("Module", function() {

        //testr trickery here
        Module = testr('someModuleToBeTested', {
            'moduleDependency':fakeDependency
        });

        describe('true',function() {
            it('should be true',function() {
                expect(true).toBe(true);
            });
        });
    });
});