Mocking ES6 module dependencies

Hello Meteor Forums,

I’ve been trying to mock module dependencies with known require Npm override packages to no avail.

I would prefer this route as opposed to using dependency injection in my modules which would require a big rewrite of most of them and changes in my coding style.

This is a list of some packages I tried:
"mock-require": "^3.0.1", "mockery": "^2.1.0", "proxyquire": "^1.8.0", "rewire": "^3.0.2", "rewiremock": "^3.4.2",

When using them either dependencies have not been mocked or the modules depended on could not be found when using relative paths while trying to mock them.

They weren’t even external dependencies just other modules I wrote which I imported to the module under test with an absolute (meteor) path similar to import moduleDependency from '/imports/api/<moduleDependencyPath>'

When using these absolute meteor paths the mocking would never work. And when trying to use relative paths the modules depended on would not be found to be mocked.

I am aware my ES6 imports are being transformed to CommonJS modules with Babel but I don’t know how this would affect the path which has to be provided to be overridden.

I am aware that most of these packages stress that they require the path provided to be overridden has to be relative to the module under test.

Has anyone been successful in using any of these packages or similar to mock dependecies of modules they are testing with meteor test?

Just fyi I am currently on version 1.6.0.1 and I don’t know if 1.6.1 would offer any benefits with Babel 7.

If anyone made it this far, thanks for reading! :slight_smile:

Hey, did you ever find a solution to this?
I’m just trying to figure it out now

I think mocking dependencies is a bad idea, try thinking in dependency injection style:

Sample:

import { Dispatcher } from 'core/events';
import ItemServiceModel from './ItemServiceModel';

export const ItemService = new ItemServiceModel({
  dispatcher: Dispatcher,
});
// @flow

type Inject = {
  dispatcher: any,
};

/**
 * Item Service
 */
export default class ItemServiceModel {
  dispatcher: any;

  /**
   * @param injection
   */
  constructor(injection: Inject) {
    Object.assign(this, injection);
  }

  create(userId: string, data: Object) {
    return userId;
  }
}
1 Like

This looks more like implementation injection than anything!

Anywhere I can read up about this pattern?

The idea behind it is straightforward, everything your service uses, gets injected via constructor, or a setter, or property, it doesn’t depend on modules (but it may depend on interfaces), and you instantiate your service elsewhere with the proper dependencies, this lets you properly test your Service, by instantiating and injecting your mocks in the test, and not relying on imports.

Read more here: http://www.meteor-tuts.com/chapters/3/services.html

1 Like

Thanks for your tips @diaconutheodor, as stated I dislike DI because it would require me to rewrite most of my modules.

I have not found a working approach for me and therefore would hugely appreciate anyone with an alternative approach to mocking dependencies or dependency injection.

Thank you.

Thankfully I can afford refactoring as recommended by meteor-tuts. Certainly makes isolation a lot easier

I’ve had success mocking some things with sinon and jest in other projects. Have you tried mocking a dependency with one of those?

@coagmano yes I had success with mocking Meteor with jest, but then I went with DI because it just felt cleaner. You’ll have to do some digging but it’s possible, I know I did it.

1 Like

Just reporting back:
I don’t know what I was doing wrong at first, but importing and mocking the dependency with sinon worked perfectly without needing any dependency injection or setup.

1 Like

I went down the DI route in the end too, thanks for the feedback!

1 Like

Hey @coagmano, been having a lot of trouble trying to spy/stub/mock using sinon and meteor, do you have a sample code on how to achieve it? been trying for the last week and so far I cannot make it work.

Thanks!

I think it’ll depend on what you’re trying to mock/stub.

Here’s a quick example of stubbing a validatedMethod:

    describe('module.create method', function() {
        let moduleServiceCreateStub, page, project, newModule;
        beforeEach(function() {
            moduleServiceCreateStub = stub(ModuleService, 'create');
            project = Factory.create('project', { owner: userId });
            page = Factory.create('page', { projectId: project._id });
            newModule = Factory.build('module', { pageId: page._id });
        });
        afterEach(function() {
            moduleServiceCreateStub.restore();
        });

        it('Calls create on ModuleService with correct args', function() {
            createModule._execute({ userId }, newModule);
            assert.isTrue(moduleServiceCreateStub.calledWith(newModule));
        });
        it('Returns the result of delegated call', function() {
            moduleServiceCreateStub.returns('aNewIdString');
            const result = createModule._execute({ userId }, newModule);
            assert.strictEqual(result, 'aNewIdString');
        });
        it('Throws when the user is not logged in', function() {
            assert.throws(() =>
                createModule._execute({ userId: null }, newModule)
            );
            assert.isFalse(moduleServiceCreateStub.called);
        });
});

and testing ModuleService.create, a method on a static class:

describe('#create', function() {
        let module, modulesInsertStub, result;

        beforeEach(function() {
            stubPageServiceUpdate = stub(PageService, 'update').returns(1);
            modulesInsertStub = stub(Modules, 'insert');
            module = Factory.build('module');
            delete module._id;

            modulesInsertStub.returns('3pd9QJYdTdSavNuxR');
            result = ModuleService.create(module);
        });
        afterEach(function() {
            modulesInsertStub.restore();
            stubPageServiceUpdate.restore();
        });

        it('returns the new Id', function() {
            assert.strictEqual(result, '3pd9QJYdTdSavNuxR');
        });
        it('calls insert on Modules', function() {
            assert.isTrue(modulesInsertStub.called);
        });
        it('calls update on PageService', function() {
            assert.isTrue(stubPageServiceUpdate.called);
        });
2 Likes