I use webpack's code splitting feature (require.ensure) to reduce the initial bundle size of my React application by loading components that are not visible on page load from a separate bundle that is loaded asynchronously.
This works perfectly, but I have trouble writing a unit test for it.
My test setup is based on Mocha, Chai and Sinon.
Here is the relevant excerpt from the code I have tried so far:
describe('When I render the component', () => {
let component,
mySandbox;
beforeEach(() => {
mySandbox = sandbox.create();
mySandbox.stub(require, 'ensure');
component = mount(<PageHeader />);
});
describe('the rendered component', () =>
it('contains the SideNav component', () =>
component.find(SideNav).should.have.length(1)
)
);
afterEach(() => mySandbox.restore());
});
When running the test, I get this error message:
"before each" hook for "contains the SideNav component": Cannot stub non-existent own property ensure
This happens because require.ensure is a method that only exists in a webpack bundle, but I'm not bundling my tests with webpack, nor do I want to, because it would create more overhead and presumably longer test execution times.
So my question is:
Is there a way to stub webpack's require.ensure with Sinon without running the tests through webpack?
Each module has its own instance of require so the only way to mock require.ensure is to have some kind of abstraction around require to get this unique require from the required module in test and then add a mock of ensure() to that require instance.
You could use babel-plugin-rewire and use getter to get require, like
const require = myComponent.__get__('require');
require.ensure = () => { /* mock here */};
I'm not 100% sure that it will work but definitely I would try to go in this direction. I recommend reading this issue on github which is related to your problem and explains a lot.
Related
I am going through a book on Test Driven Development in React. I've never written JavaScript tests before. The author presents the following Jest code in a file titled calc.test.js:
var add = require('./calc.js')
describe('calculator',function() {
it('add two numbers',function() {
expect(add(1,2)).toEqual(3)
})
})
but VS code automatically translates it to:
const { hasUncaughtExceptionCaptureCallback } = require('process')
const { isTypedArray } = require('util/types')
var add = require('./calc.js')
describe('calculator', function () {
isTypedArray('add two numbers', function () {
hasUncaughtExceptionCaptureCallback(add(1, 2).toEqual(3))
})
})
The author states that his version uses syntax "borrowed from" jasmine. Is that why VS Code changed it? How do I turn this feature off? Jest is installed.
Seems like vscode tries to autocomplete it and expect, and auto imports the modules process and utils/types.
Even though manually importing isn't required per the jest documentation:
In your test files, Jest puts each of these methods and objects into
the global environment. You don't have to require or import anything
to use them. However, if you prefer explicit imports, you can do
import {describe, expect, test} from '#jest/globals'.
You can silence vscode warnings by explicitly importing:
import {describe, expect, test} from '#jest/globals'
In my main app, I am bringing in the node package "ibm_db" using:
import * as ibmdb from "ibm_db";
In my unit tests I want to be able to override this, then I:
import * as ibmdb from "ibm_db";
in my unit test and then:
beforeEach(() => {
ibmdb.open = jasmine.createSpy("open");
});
I get error:
Cannot assign to 'open' because it is a readOnly property.
I need to know with typescript (being compiled into js using tsc, then tested using jasmine command), the right way to mock these functions so I can tell if there being called, I don't want the calls to actually fire.
For ES6 imports use:
import * as name from "library_name"
and in the unit tests use:
const name = require("library_name");
Requires will let you overwrite parts of the library where imports will not
I have seen questions referring to the mocking of default exports with jest around here, but I don't think this has already been asked:
When mocking the default export of a dependency of a module that is being tested, the tests suite fails to run if the module imports the dependency with the ES6 import statement, stating TypeError: (0 , _dependency.default) is not a function It succeeds, however, if the module uses a require().default call instead.
In my understanding, import module from location directly translates to const module = require(location).default, so I am very confused why this is happening. I'd rather keep my code style consistent and not use the require call in the original module.
Is there a way to do it?
Test file with mock:
import './modules.js';
import dependency from './dependency';
jest.mock('./dependency', () => {
return {
default: jest.fn()
};
});
// This is what I would eventually like to call
it('calls the mocked function', () => {
expect(dependency).toHaveBeenCalled();
});
Dependency.js
export default () => console.log('do something');
module.js (not working)
import dependency from './dependency.js';
dependency();
module.js (working)
const dependency = require('./dependency.js').default;
dependency();
You can use either es6 import or require js to import your js files in your jest tests.
When using es6 import you should know that jest is trying to resolve all the dependencies and also calls the constructor for the class that you are importing. During this step, you cannot mock it. The dependency has to be successfully resolved, and then you can proceed with mocks.
I should also add that as can be seen here jest by default hoists any jest.mocks to the top of the file so the order in which you place your imports does not really matter.
Your problem though is different. Your mock function assumes that you have included your js file using require js.
jest.mock('./dependecy', () => {
return {
default: jest.fn()
};
});
When you import a file using require js, this is the structure it has:
So assuming I have imported my class called "Test" using require js, and it has method called "doSomething" I could call it in my test by doing something like:
const test = require('../Test');
test.default.doSomething();
When importing it using es6 import, you should do it differently though. Using the same example:
import Test from '../Test';
Test.doSomething();
EDIT: If you want to use es6 import change your mock function to:
jest.mock('./dependecy', () => jest.fn());
the short answer for ES module if you want to use
import dependency from 'dependency'
jest.mock('dependency', () => ({
...jest.requireActual('dependency'),
__esModule: true,
default: jest.fn(),
}))
Have you tried something like this? I was dealing with the default export mocking for months until I found this.
jest.mock('./dependency', () => () => jest.fn());
The idea behind this line is that you are exporting a module that is a function. So you need to let Jest knows that it has to mock all your ./dependency file as a function, that returns a jest.fn()
First of all, for testing my library, I'm using Mocha and Chai, but I'm probably going to need Sinon too sometime.
This is the library:
import Service from 'service'; // a third-party module out of my control
const service = Service(...);
class MyLib {
... uses `service` in a bunch of different ways ...
... service.put(foo) ...
... service.get(bar) ...
}
export default MyLib;
This is basically the test file:
import MyLib from '../my-lib.js';
describe('MyLib', () => {
describe('a method that uses the service', () => {
...
The service object makes some calls to remote servers, which I can't really do in the tests. Therefore, I'm thinking I should stub the service's methods or mock the entire service object. However, since the object is constant and only reachable through the MyLib closure, I don't know how.
Ideally I don't wish to change the API of MyLib to e.g. inject the service object in the constructor.
I use Babel 6 with the es2015 preset, if it matters.
How should I approach this?
There are a few ways to do it.
The simplest way without extra libraries
Save service as a class property and call it from there:
import Service from 'service';
const service = Service(...);
class MyLib {
constructor() {
this.service = service;
}
... now you should call service in a bit different way
... this.service.put(foo) ...
... this.service.get(bar) ...
}
export default MyLib;
Then you can rewrite service instance in your tests:
it('should call my mock', () => {
const lib = new MyLib();
lib.service = mockedService; // you need to setup this mock, with Sinon, for example
lib.doSomething();
assert.ok(mockedService.put.calledOnce); // works
});
Mock require() function
There are some libraries that allow you to override results of require() function. My favourite one is proxyquire. You can use it and your module will get mockedSerice instead of real:
import proxyquire from 'proxyquire';
it('should call my mock', () => {
const MyLib = proxyquire('./my-lib', {
// pass here the map of mocked imports
service: mockedService
})
const lib = new MyLib();
lib.doSomething();
assert.ok(mockedService.put.calledOnce); // works
});
Use rewire to get access into module closure
Rewire is a special library that instruments module code so then you can change any local variable there
import rewire from 'rewire';
it('should call my mock', () => {
const MyLib = rewire('./my-lib')
const lib = new MyLib();
// __set__ is a special secret method added by rewire
MyLib.__set__('service', mockedService);
lib.doSomething();
assert.ok(mockedService.put.calledOnce); // works
});
Also, there is a babel-plugin-rewire for better integration with your tools.
All methods above are nice you may pick that seems better for your issue.
I was dealing with the same thing recently.
You can take advantage of https://nodejs.org/api/modules.html#modules_caching
In the test you are writing, require/import the service the same way you do in the file you are testing.
Then use Sinon to stub the methods you are using within the file to test.
sinon.stub(Service, put).returns()
When the file requires the service, it will use the modified module.
I haven't tested your exact case, where you are creating instance of the service and only after that you work with it, but a bit of playing with it should help you achieve what you want, and it is all without any external libraries, only simple sinon stub.
Your tests for MyLib shouldn't test service, so I would suggest mocking all or most of it. You should just check that MyLib is calling the right functions on service with the right arguments.
I'm not sure about sinon, but that looks like a pretty standard way to import and use a library, I would be surprised if it doesn't support mocking service.
I am running mocha tests on my server, testing source scripts an isolated unit test manner.
One of the scripts I am testing makes a call to Webpack's require.ensure function, which is useful for creating code-splitting points in the application when it gets bundled by Webpack.
The test I have written for this script does not run within a Webpack context, therefore the require.ensure function does not exist, and the test fails.
I have tried to create some sort of polyfill/stub/mock/spy for this function but have had no luck whatsoever.
There is a package, webpack-require, which does allow for the creation of a webpack context. This can work but it is unacceptably slow. I would prefer to have some sort of lightweight polyfill targeting the require.ensure function directly.
Any recommendations? :)
Here is a very basic starting point mocha test.
The mocha test loads a contrived module containing a method which returns true if require.ensure is defined.
foo.js
export default {
requireEnsureExists: () => {
return typeof require.ensure === 'function';
}
};
foo.test.js
import { expect } from 'chai';
describe('When requiring "foo"', () => {
let foo;
before(() => {
foo = require('./foo.js');
});
it('The requireEnsureExists() should be true', () => {
expect(foo.requireEnsureExists()).to.be.true;
});
});
Ok, I finally have an answer for this after much research and deliberation.
I initially thought that I could solve this using some sort of IoC / DI strategy, but then I found the source code for Node JS's Module library which is responsible for loading modules. Looking at the source code you will notice that the 'require' function for modules (i.e. foo.js in my example) get created by the _compile function of NodeJs's module loader. It's internally scoped and I couldn't see an immediate mechanism by which to modify it.
I am not quite sure how or where Webpack is extending the created "require" instance, but I suspect it is with some black magic. I realised that I would need some help to do something of a similar nature, and didn't want to write a massive block of complicated code to do so.
Then I stumbled on rewire...
Dependency injection for node.js applications.
rewire adds a special setter and getter to modules so you can modify their behaviour for better unit testing. You may
inject mocks for other modules
leak private variables
override variables within the module.
rewire does not load the file and eval the contents to emulate node's require mechanism. In fact it uses node's own require to load the module. Thus your module behaves exactly the same in your test environment as under regular circumstances (except your modifications).
Perfect. Access to private variables is all that I need.
After installing rewire, getting my test to work was easy:
foo.js
export default {
requireEnsureExists: () => {
return typeof require.ensure === 'function';
}
};
foo.test.js
import { expect } from 'chai';
import rewire from 'rewire';
describe('When requiring "foo"', () => {
let foo;
before(() => {
foo = rewire('./foo.js');
// Get the existing 'require' instance for our module.
let fooRequire = moduletest.__get__('require');
// Add an 'ensure' property to it.
fooRequire.ensure = (path) => {
// Do mocky/stubby stuff here.
};
// We don't need to set the 'require' again in our module, as the above
// is by reference.
});
it('The requireEnsureExists() should be true', () => {
expect(foo.requireEnsureExists()).to.be.true;
});
});
Aaaaah.... so happy. Fast running test land again.
Oh, in my case it's not needed, but if you are bundling your code via webpack for browser based testing, then you may need the rewire-webpack plugin. I also read somewhere that this may have problems with ES6 syntax.
Another note: for straight up mocking of require(...) statements I would recommend using mockery instead of rewire. It's less powerful than rewire (no private variable access), but this is a bit safer in my opinion. Also, it has a very helpful warning system to help you not do any unintentional mocking.
Update
I've also seen the following strategy being employed. In every module that uses require.ensure check that it exists and polyfill it if not:
// Polyfill webpack require.ensure.
if (typeof require.ensure !== `function`) require.ensure = (d, c) => c(require);