Jest: Testing exported modules in the same file

Created on 26 May 2016  Â·  7Comments  Â·  Source: facebook/jest

How to test the following:

// File: index.js

export function myName(text) {
    return 'My name is: ' + text;
}

export function makeBold(text) {
    return '<strong>' + myName(text) + '</strong>';
}

With the following test:

// File: __tests__/foo.test.js

jest.unmock('../index');

import { makeBold, myName } from '../index';

describe('bar()', () => {

  it('calls myName', () => {
    myName = jest.fn();
    makeBold('Text');
    expect(myName).toBeCalled();
  });

});

The above would throw similar to:

... myName is read-only ...

Most helpful comment

Jest mocks things on the module boundary, which is the boundary at which you should test your "units" (= modules). With JavaScript, there is no way to swap out references to something.

Consider this example:

// a.js
exports.a = 42;

// b.js
var b = require('./a').a;
b = 14;
console.log(require('./a').a); // still 42

this is a simple example similar to your code that should point out the problem you are having. You cannot swap out a function that is internal to a module. When you try to overwrite myName with jest.fn() in your example, you are just modifying the local binding myName in your test but it has no effect on the myName binding in your other file. This unfortunately becomes more of a problem with ES2015 modules where it is less obvious. The right solution here is to split up myName and makeBold into two separate modules and mock myName. Generally, there is little value in writing a test like the one you are writing here: you should test that makeBold _behaves_ as expected, not that its internal implementation relies on any particular implementation details.

One way however to do what you are trying to do is to drop down to CommonJS (which babel compiles to, anyway):

// module.js
exports.foo = function() {};
exports.bar = function() { exports.foo(); }

// test.js
const module = require('./module');
module.foo = jest.fn();
module.bar();
expect(module.foo).toBeCalled();

This works because the bar function calls foo with a binding that can be modified – exports.foo is just a field on an object that can freely be modified.

I hope this explanation makes sense and I provided enough guidance so that you can write a good test. Please note that this is mostly a limitation of ES2015 (modules) and we can't really do much about this. Also, this is a bug tracker that we use to prioritize work. It's not well-suited for questions. In the future, please ask questions on StackOverflow.

All 7 comments

Jest mocks things on the module boundary, which is the boundary at which you should test your "units" (= modules). With JavaScript, there is no way to swap out references to something.

Consider this example:

// a.js
exports.a = 42;

// b.js
var b = require('./a').a;
b = 14;
console.log(require('./a').a); // still 42

this is a simple example similar to your code that should point out the problem you are having. You cannot swap out a function that is internal to a module. When you try to overwrite myName with jest.fn() in your example, you are just modifying the local binding myName in your test but it has no effect on the myName binding in your other file. This unfortunately becomes more of a problem with ES2015 modules where it is less obvious. The right solution here is to split up myName and makeBold into two separate modules and mock myName. Generally, there is little value in writing a test like the one you are writing here: you should test that makeBold _behaves_ as expected, not that its internal implementation relies on any particular implementation details.

One way however to do what you are trying to do is to drop down to CommonJS (which babel compiles to, anyway):

// module.js
exports.foo = function() {};
exports.bar = function() { exports.foo(); }

// test.js
const module = require('./module');
module.foo = jest.fn();
module.bar();
expect(module.foo).toBeCalled();

This works because the bar function calls foo with a binding that can be modified – exports.foo is just a field on an object that can freely be modified.

I hope this explanation makes sense and I provided enough guidance so that you can write a good test. Please note that this is mostly a limitation of ES2015 (modules) and we can't really do much about this. Also, this is a bug tracker that we use to prioritize work. It's not well-suited for questions. In the future, please ask questions on StackOverflow.

you should test that makeBold behaves as expected, not that its internal implementation relies on any particular implementation details.

So true, thats basically what we did in the end.

Apologies for logging an issue, I understand such"questions" are not suited for an issue tracker. Although at times its hard to differ the two.

That's alright! Now we have this documented somewhere, so that's good for the next person who runs into this :)

@danieldiekmeier, could you provide more information how spyOn could solve raise issue, please?

leave me an answer if it work for es6

Sorry to comment an old issue. Please forgive if it has broken any rules 😅

I was wondering what the test would look like to achieve:

you should test that makeBold behaves as expected, not that its internal implementation relies on any particular implementation details.

Does it mean we directly assert the return value of makeBold function ? In stead of asserting it has called myName function ?

To anybody coming here looking for answers like I was, check out babel-plugin-rewire.

Was this page helpful?
0 / 5 - 0 ratings