How to make Jest test client-side scripts? - javascript

I've got some client-side code that I'd like to test with Jest. Let's narrow it down to this reproducible bit:
// fetch-example.js
const fetchExample = () => fetch('http://example.org/');
module.exports = fetchExample
Since Jest runs tests via Node and Node doesn't support fetch, the following test script
// fetch-example.test.js
const fetchExample = require('./fetch-example')
test('fetch resolves to something', () => {
expect.assertions(1);
return fetchExample().then(() => expect(true).toBe(true));
});
fails with ReferenceError: fetch is not defined. No surprises, but... fetchExample works fine in a browser and I'd like Jest to take this into account and mark the test as passed.
Is there any way to make this work? Is there any parameter I can pass to Jest to include browser functions like fetch?
NB: Jest's browser configuration entry does not make it work.

Related

How to create Jest Custom Environment with Typescript?

I am trying to create an extension off jest-node-environment as a CustomTestEnvironment but am getting the following error when trying to run jest
● Test suite failed to run
~/git/my-application/tests/environment/custom-test-environment.ts:1
import NodeEnvironment from 'jest-environment-node';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at runTestInternal (../node_modules/jest-runner/build/runTest.js:226:5)
I believe this error means it doesn't recognize this as a typescript file, and hasn't transpiled it. ( I am using the latest version of jest 26.0.1)
Based on discussions in the jest github, the PR to make this work was slated for Jest 26 but was pulled from Jest 26 and is (hopefully) going to be in Jest 27.
https://github.com/facebook/jest/pull/8751
That being said, I've seen samples of people doing it this way online but for me I'm not having any luck following their lead.
import NodeEnvironment from "jest-environment-node";
import {LocalObject} from "./object/local-object.helper";
export class CustomTestEnvironment extends NodeEnvironment {
public async setup(): Promise<void> {
await super.setup();
this.global.localObject = LocalObject.init()
}
public async teardown(): Promise<void> {
LocalObject.teardown(this.global.localObject)
await super.teardown();
}
}
The LocalObject is just a thin wrapper around a test utility which has a complex startup and teardown and which I want to provide to tests to be able to publish test data and kick off the component test.
If I change the imports to be require -
const NodeEnvironment = require("jest-environment-node");
const {LocalObject} = require("./object/local-object.helper");
Then I get this error instead -
SyntaxError: Unexpected token 'export'
If I move the export into an module.exports then I get the following error
public async setup(): Promise<void> {
^^^^^
SyntaxError: Unexpected token 'async'
Seems to me like it's not treating this file as typescript.
Is there any workaround to be able to use this as a typescript file? The LocalObject is written in typescript, so I believe that I need this to be typescript to work right with that one, and it is important that LocalObject file remains typescript for test files to use it correctly.
Alternative question:
Can I do the same kind of setup/teardown logic in the setupFilesAfterEnv I only saw that they were run before, but not after tests. Thanks.
UPDATE: Jest 27 is now released and now supports this. If you are using an older version of Jest, update to Jest 27 to be able to use CustomEnvironment as typescript https://jestjs.io/blog/2021/05/25/jest-27#features-coming-with-breaking-changes
Modules used in the following configuration options are now transformed like the rest of your code, which may be breaking if you relied on them being loaded as-is:
testEnvironment
runner
testRunner
snapshotResolver
Alternatively, you can continue to use the workaround below for prior versions.
This isn't supported in Jest 26 and is slated to be in Jest 27 https://github.com/facebook/jest/pull/8751#issuecomment-699049851
For now the solution is to use setupFilesAfterEnv files in my jest.config https://jestjs.io/docs/en/configuration#setupfilesafterenv-array.
From there I could put all my my setup/teardown in beforeAll() and afterAll() blocks. This was effectively equivalent to using the Node Environment and the setupFilesAfterEnv files is compatible with typescript.
//jest.setup.ts
import {LocalObject} from "./object/local-object.helper";
let localObject: LocalObject;
beforeAll(async () => {
//Start my environment or seed data to DB or whatever
localObject = await LocalObject.init()
}
afterAll(async () => {
//teardown or clean things started in setup my environment
await localObject.teardown()
}

Jest virtual mock: how do I troubleshoot this failure?

So I have this import statement in a module that I'm trying to test using jest 25.1 running on node 11.1.0. The import statement is for a module that is only available when running on the jvm's nashorn runtime, so I'm using jest's virtual mock to control the behavior in the unit tests. Here's what the import statement looks like in the module under test:
import RequestBuilder from 'nashorn-js/request_builder'
...and after the other lines in the import block, this:
const request = RequestBuilder.create('some-string')
.sendTo('some-other-string')
.withAction('yet-another-string')
.getResultWith( consumer => consumer.result( consumer.message().body() ) )
export const functionA = () => {...} // uses 'request' variable
export const functionB = () => {...} // uses 'request' variable
In the corresponding .spec file, I have this virtual mock setup:
const mockGetResultWith = {
getResultWith: jest.fn()
}
const mockWithAction = {
withAction: jest.fn().mockImplementation(() => mockGetResultWith)
}
const mockSendTo = {
sendTo: jest.fn().mockImplementation(() => mockWithAction)
}
const mockBuilder = {
create: jest.fn().mockImplementation(() => mockSendTo)
}
jest.mock(
'nashorn-js/request_builder',
() => mockBuilder,
{ virtual: true }
)
require('nashorn-js/request_builder')
import { functionA, functionB } from './module-under-test'
I have been trying unsuccessfully to get past this failure from jest:
● Test suite failed to run
TypeError: Cannot read property 'create' of undefined
35 | }
36 |
> 37 | const verify = RequestBuilder.create('some-string')
| ^
38 | .sendTo('some-other-string')
39 | .withAction('yet-another-string')
40 | .getResultWith( consumer => consumer.result( consumer.message().body() ) )
I've tried all kinds of different mock structures, using require vs import, etc, but haven't found the magic bullet.
As near as I can tell, it does not appear that the RequestBuilder import statement is even invoking the virtual mock. Or at least, if I add console.log() statements to the virtual mock factory function, I never see those log messages in the output.
Anybody have any idea what I'm missing or what else to try? I have pretty much the same pattern in use in other parts of the code, where this setup works, but for some mystical reason with this module, I can't get the virtual mock working. Any help is greatly appreciated.
So, the problem here turned out to be jest's implementation of import vs require in the .spec file.
By changing this line:
import { functionA, functionB } from './module-under-test'
To this:
const module = require('./module-under-test')
const functionA = module.functionA
const functionB = module.functionB
The module under test now loads successfully, and the tests run as expected.
I have no explanation for this, and haven't been able to find jest documentation describing why I'd get any different behavior between require vs import. In fact, I have the mock configuration setup before any import statements as described here:
https://github.com/kentcdodds/how-jest-mocking-works
If anybody out there understands what's going on with this import behavior, or has a link describing what I'm missing, I'd sure appreciate the info.

Jest mocking module implementation with .mockImplemetation

Can anyone help here. I am really frustrated with how the mockImplementation works.
So, first of all I am using jest for node testing. I am using the commonjs modules. what I wanna do is that I am trying to mock a module using mockImplementation() and change its implementation between different tests according to this documentation here: https://jestjs.io/docs/en/es6-class-mocks#replacing-the-mock-using-mockimplementation-docs-en-mock-function-api-mockfnmockimplementationfn-or-mockimplementationonce-docs-en-mock-function-api-mockfnmockimplementationoncefn.
My code look like this:
const exportBigQueryTableModule =require('../repository/exportBigQueryTable')
jest.mock('../repository/exportBigQueryTable')
describe('deleting files on table export fail', () => {
mockExportBigQueryTable = jest
.fn(() => Promise.resolve())
.mockResolvedValueOnce()
.mockRejectedValueOnce(new Error('Could not export table'))
exportBigQueryTableModule.mockImplementation(mockExportBigQueryTable)
it(' should do some test', () => {})
})
The problem here is that looks like that this line jest.mock('../repository/exportBigQueryTable') create for me a default mock kind of jest.fn() and the module is always loaded with that default function. So the mock function that I did provide on the test using the mockImplementation never overrides the previous one, I do not get what is the problem here. Why the same exmaple on the official documentation works the only difference is that it uses es6 modules on the doc sample.
I am not sure if I am missing something here.

Jest snapshots return the same code for every tested file

Im running jest enzyme tests, with snapshots. The snapshot files are generated correctly, however their content is basically:
// Jest Snapshot v1, https;//link
exports[`Component1 should match snapshot 1`] = `ReactWrapper {}`;
^^^^^^^^ <-- only component name is changing
for every tested file, even if tested files have complicated logic and 400+ lines of code.
Why does it happen? Where's the whole code?
it('should match snapshot', () => {
const component = mount(<Component1 />);
expect(component).toMatchSnapshot();
});
I'm looking at my own tests and it seems the enzyme-to-json package is needed to create useful snapshots with Jest. This will serialize your data for use with Jest.

How to unit test a Node.js module that requires other modules and how to mock the global require function?

This is a trivial example that illustrates the crux of my problem:
var innerLib = require('./path/to/innerLib');
function underTest() {
return innerLib.doComplexStuff();
}
module.exports = underTest;
I am trying to write a unit test for this code. How can I mock out the requirement for the innerLib without mocking out the require function entirely?
So this is me trying to mock out the global require and finding out that it won’t work even to do that:
var path = require('path'),
vm = require('vm'),
fs = require('fs'),
indexPath = path.join(__dirname, './underTest');
var globalRequire = require;
require = function(name) {
console.log('require: ' + name);
switch(name) {
case 'connect':
case indexPath:
return globalRequire(name);
break;
}
};
The problem is that the require function inside the underTest.js file has actually not been mocked out. It still points to the global require function. So it seems that I can only mock out the require function within the same file I’m doing the mocking in. If I use the global require to include anything, even after I’ve overridden the local copy, the files being required will still have the global require reference.
You can now!
I published proxyquire which will take care of overriding the global require inside your module while you are testing it.
This means you need no changes to your code in order to inject mocks for required modules.
Proxyquire has a very simple api which allows resolving the module you are trying to test and pass along mocks/stubs for its required modules in one simple step.
#Raynos is right that traditionally you had to resort to not very ideal solutions in order to achieve that or do bottom-up development instead
Which is the main reason why I created proxyquire - to allow top-down test driven development without any hassle.
Have a look at the documentation and the examples in order to gauge if it will fit your needs.
A better option in this case is to mock methods of the module that gets returned.
For better or worse, most node.js modules are singletons; two pieces of code that require() the same module get the same reference to that module.
You can leverage this and use something like sinon to mock out items that are required. mocha test follows:
// in your testfile
var innerLib = require('./path/to/innerLib');
var underTest = require('./path/to/underTest');
var sinon = require('sinon');
describe("underTest", function() {
it("does something", function() {
sinon.stub(innerLib, 'toCrazyCrap').callsFake(function() {
// whatever you would like innerLib.toCrazyCrap to do under test
});
underTest();
sinon.assert.calledOnce(innerLib.toCrazyCrap); // sinon assertion
innerLib.toCrazyCrap.restore(); // restore original functionality
});
});
Sinon has good integration with chai for making assertions, and I wrote a module to integrate sinon with mocha to allow for easier spy/stub cleanup (to avoid test pollution.)
Note that underTest cannot be mocked in the same way, as underTest returns only a function.
Another option is to use Jest mocks. Follow up on their page
I use mock-require. Make sure you define your mocks before you require the module to be tested.
Simple code to mock modules for the curious
Notice the parts where you manipulate the require.cache and note require.resolve method as this is the secret sauce.
class MockModules {
constructor() {
this._resolvedPaths = {}
}
add({ path, mock }) {
const resolvedPath = require.resolve(path)
this._resolvedPaths[resolvedPath] = true
require.cache[resolvedPath] = {
id: resolvedPath,
file: resolvedPath,
loaded: true,
exports: mock
}
}
clear(path) {
const resolvedPath = require.resolve(path)
delete this._resolvedPaths[resolvedPath]
delete require.cache[resolvedPath]
}
clearAll() {
Object.keys(this._resolvedPaths).forEach(resolvedPath =>
delete require.cache[resolvedPath]
)
this._resolvedPaths = {}
}
}
Use like:
describe('#someModuleUsingTheThing', () => {
const mockModules = new MockModules()
beforeAll(() => {
mockModules.add({
// use the same require path as you normally would
path: '../theThing',
// mock return an object with "theThingMethod"
mock: {
theThingMethod: () => true
}
})
})
afterAll(() => {
mockModules.clearAll()
})
it('should do the thing', async () => {
const someModuleUsingTheThing = require('./someModuleUsingTheThing')
expect(someModuleUsingTheThing.theThingMethod()).to.equal(true)
})
})
BUT... jest has this functionality built in and I recommend that testing framework over rolling your own for testing purposes.
Mocking require feels like a nasty hack to me. I would personally try to avoid it and refactor the code to make it more testable.
There are various approaches to handle dependencies.
1) pass dependencies as arguments
function underTest(innerLib) {
return innerLib.doComplexStuff();
}
This will make the code universally testable. The downside is that you need to pass dependencies around, which can make the code look more complicated.
2) implement the module as a class, then use class methods/ properties to obtain dependencies
(This is a contrived example, where class usage is not reasonable, but it conveys the idea)
(ES6 example)
const innerLib = require('./path/to/innerLib')
class underTestClass {
getInnerLib () {
return innerLib
}
underTestMethod () {
return this.getInnerLib().doComplexStuff()
}
}
Now you can easily stub getInnerLib method to test your code.
The code becomes more verbose, but also easier to test.
If you've ever used jest, then you're probably familiar with jest's mock feature.
Using "jest.mock(...)" you can simply specify the string that would occur in a require-statement in your code somewhere and whenever a module is required using that string a mock-object would be returned instead.
For example
jest.mock("firebase-admin", () => {
const a = require("mocked-version-of-firebase-admin");
a.someAdditionalMockedMethod = () => {}
return a;
})
would completely replace all imports/requires of "firebase-admin" with the object you returned from that "factory"-function.
Well, you can do that when using jest because jest creates a runtime around every module it runs and injects a "hooked" version of require into the module, but you wouldn't be able to do this without jest.
I have tried to achieve this with mock-require but for me it didn't work for nested levels in my source. Have a look at the following issue on github: mock-require not always called with Mocha.
To address this I have created two npm-modules you can use to achieve what you want.
You need one babel-plugin and a module mocker.
babel-plugin-mock-require
jestlike-mock
In your .babelrc use the babel-plugin-mock-require plugin with following options:
...
"plugins": [
["babel-plugin-mock-require", { "moduleMocker": "jestlike-mock" }],
...
]
...
and in your test file use the jestlike-mock module like so:
import {jestMocker} from "jestlike-mock";
...
jestMocker.mock("firebase-admin", () => {
const firebase = new (require("firebase-mock").MockFirebaseSdk)();
...
return firebase;
});
...
The jestlike-mock module is still very rudimental and does not have a lot of documentation but there's not much code either. I appreciate any PRs for a more complete feature set. The goal would be to recreate the whole "jest.mock" feature.
In order to see how jest implements that one can look up the code in the "jest-runtime" package. See https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/index.js#L734 for example, here they generate an "automock" of a module.
Hope that helps ;)
You can't. You have to build up your unit test suite so that the lowest modules are tested first and that the higher level modules that require modules are tested afterwards.
You also have to assume that any 3rd party code and node.js itself is well tested.
I presume you'll see mocking frameworks arrive in the near future that overwrite global.require
If you really must inject a mock you can change your code to expose modular scope.
// underTest.js
var innerLib = require('./path/to/innerLib');
function underTest() {
return innerLib.toCrazyCrap();
}
module.exports = underTest;
module.exports.__module = module;
// test.js
function test() {
var underTest = require("underTest");
underTest.__module.innerLib = {
toCrazyCrap: function() { return true; }
};
assert.ok(underTest());
}
Be warned this exposes .__module into your API and any code can access modular scope at their own danger.
You can use mockery library:
describe 'UnderTest', ->
before ->
mockery.enable( warnOnUnregistered: false )
mockery.registerMock('./path/to/innerLib', { doComplexStuff: -> 'Complex result' })
#underTest = require('./path/to/underTest')
it 'should compute complex value', ->
expect(#underTest()).to.eq 'Complex result'
I use a simple factory the returns a function that calls a function with all of its dependencies:
/**
* fnFactory
* Returns a function that calls a function with all of its dependencies.
*/
"use strict";
const fnFactory = ({ target, dependencies }) => () => target(...dependencies);
module.exports = fnFactory;
Wanting to test the following function:
/*
* underTest
*/
"use strict";
const underTest = ( innerLib, millions ) => innerLib.doComplexStuff(millions);
module.exports = underTest;
I would setup my test (I use Jest) as follows:
"use strict";
const fnFactory = require("./fnFactory");
const _underTest = require("./underTest");
test("fnFactory can mock a function by returng a function that calls a function with all its dependencies", () => {
const fake = millions => `Didn't do anything with ${millions} million dollars!`;
const underTest = fnFactory({ target: _underTest, dependencies: [{ doComplexStuff: fake }, 10] });
expect(underTest()).toBe("Didn't do anything with 10 million dollars!");
});
See results of test
In production code I would manually inject the callee's dependencies as below:
/**
* main
* Entry point for the real application.
*/
"use strict";
const underTest = require("./underTest");
const innerLib = require("./innerLib");
underTest(innerLib, 10);
I tend to limit the scope of most of the modules that I write to one thing, which reduces the number of dependencies that have to be accounted for when testing and integrating them into the project.
And that's my approach to dealing with dependencies.

Categories