Test React component with imported functions [duplicate] - javascript

I want to test that one of my ES6 modules calls another ES6 module in a particular way. With Jasmine this is super easy --
The application code:
// myModule.js
import dependency from './dependency';
export default (x) => {
dependency.doSomething(x * 2);
}
And the test code:
//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
spyOn(dependency, 'doSomething');
myModule(2);
expect(dependency.doSomething).toHaveBeenCalledWith(4);
});
});
What's the equivalent with Jest? I feel like this is such a simple thing to want to do, but I've been tearing my hair out trying to figure it out.
The closest I've come is by replacing the imports with requires, and moving them inside the tests/functions. Neither of which are things I want to do.
// myModule.js
export default (x) => {
const dependency = require('./dependency'); // Yuck
dependency.doSomething(x * 2);
}
//myModule-test.js
describe('myModule', () => {
it('calls the dependency with double the input', () => {
jest.mock('../dependency');
myModule(2);
const dependency = require('../dependency'); // Also yuck
expect(dependency.doSomething).toBeCalledWith(4);
});
});
For bonus points, I'd love to make the whole thing work when the function inside dependency.js is a default export. However, I know that spying on default exports doesn't work in Jasmine (or at least I could never get it to work), so I'm not holding out hope that it's possible in Jest either.

Edit: Several years have passed and this isn't really the right way to do this any more (and probably never was, my bad).
Mutating an imported module is nasty and can lead to side effects like tests that pass or fail depending on execution order.
I'm leaving this answer in its original form for historical purposes, but you should really use jest.spyOn or jest.mock. Refer to the jest docs or the other answers on this page for details.
Original answer follows:
I've been able to solve this by using a hack involving import *. It even works for both named and default exports!
For a named export:
// dependency.js
export const doSomething = (y) => console.log(y)
// myModule.js
import { doSomething } from './dependency';
export default (x) => {
doSomething(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
dependency.doSomething = jest.fn(); // Mutate the named export
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});
Or for a default export:
// dependency.js
export default (y) => console.log(y)
// myModule.js
import dependency from './dependency'; // Note lack of curlies
export default (x) => {
dependency(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
dependency.default = jest.fn(); // Mutate the default export
myModule(2);
expect(dependency.default).toBeCalledWith(4); // Assert against the default
});
});

You have to mock the module and set the spy by yourself:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency', () => ({
doSomething: jest.fn()
}))
describe('myModule', () => {
it('calls the dependency with double the input', () => {
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});

Fast forwarding to 2020, I found this blog post to be the solution: Jest mock default and named export
Using only ES6 module syntax:
// esModule.js
export default 'defaultExport';
export const namedExport = () => {};
// esModule.test.js
jest.mock('./esModule', () => ({
__esModule: true, // this property makes it work
default: 'mockedDefaultExport',
namedExport: jest.fn(),
}));
import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function
Also one thing you need to know (which took me a while to figure out) is that you can't call jest.mock() inside the test; you must call it at the top level of the module. However, you can call mockImplementation() inside individual tests if you want to set up different mocks for different tests.

To mock an ES6 dependency module default export using Jest:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');
// If necessary, you can place a mock implementation like this:
dependency.mockImplementation(() => 42);
describe('myModule', () => {
it('calls the dependency once with double the input', () => {
myModule(2);
expect(dependency).toHaveBeenCalledTimes(1);
expect(dependency).toHaveBeenCalledWith(4);
});
});
The other options didn't work for my case.

Adding more to Andreas' answer. I had the same problem with ES6 code, but I did not want to mutate the imports. That looked hacky. So I did this:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');
describe('myModule', () => {
it('calls the dependency with double the input', () => {
myModule(2);
});
});
And added file dependency.js in the " __ mocks __" folder parallel to file dependency.js. This worked for me. Also, this gave me the option to return suitable data from the mock implementation. Make sure you give the correct path to the module you want to mock.

The question is already answered, but you can resolve it like this:
File dependency.js
const doSomething = (x) => x
export default doSomething;
File myModule.js
import doSomething from "./dependency";
export default (x) => doSomething(x * 2);
File myModule.spec.js
jest.mock('../dependency');
import doSomething from "../dependency";
import myModule from "../myModule";
describe('myModule', () => {
it('calls the dependency with double the input', () => {
doSomething.mockImplementation((x) => x * 10)
myModule(2);
expect(doSomething).toHaveBeenCalledWith(4);
console.log(myModule(2)) // 40
});
});

None of the answers here seemed to work for me (the original function was always being imported rather than the mock), and it seems that ESM support in Jest is still work in progress.
After discovering this comment, I found out that jest.mock() does not actually work with regular imports, because the imports are always run before the mock (this is now also officially documented). Because of this, I am importing my dependencies using await import(). This even works with a top-level await, so I just have to adapt my imports:
import { describe, expect, it, jest } from '#jest/globals';
jest.unstable_mockModule('../dependency', () => ({
doSomething: jest.fn()
}));
const myModule = await import('../myModule');
const dependency = await import('../dependency');
describe('myModule', async () => {
it('calls the dependency with double the input', () => {
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});

I solved this another way. Let's say you have your dependency.js
export const myFunction = () => { }
I create a depdency.mock.js file besides it with the following content:
export const mockFunction = jest.fn();
jest.mock('dependency.js', () => ({ myFunction: mockFunction }));
And in the test, before I import the file that has the dependency, I use:
import { mockFunction } from 'dependency.mock'
import functionThatCallsDep from './tested-code'
it('my test', () => {
mockFunction.returnValue(false);
functionThatCallsDep();
expect(mockFunction).toHaveBeenCalled();
})

I tried all the solutions and none worked or were showing lots of TS errors.
This is how I solved it:
format.ts file:
import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'
class Format {
parseXml (xml: string) {
return camelcaseKeys(parse(xml), {
deep: true,
})
}
}
const format = new Format()
export { format }
format.test.ts file:
import format from './format'
import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'
jest.mock('xml-parser', () => jest.fn().mockReturnValue('parsed'))
jest.mock('camelcase-keys', () => jest.fn().mockReturnValue('camel cased'))
describe('parseXml', () => {
test('functions called', () => {
const result = format.parseXml('XML')
expect(parse).toHaveBeenCalledWith('XML')
expect(camelcaseKeys).toHaveBeenCalledWith('parsed', { deep: true })
expect(result).toBe('camel cased')
})
})

I made some modifications on #cam-jackson original answer and side effects has gone. I used lodash library to deep clone the object under test and then made any modification I want on that object. But be ware that cloning heavy objects can have negative impact on test performance and test speed.
objectUndertest.js
const objectUnderTest = {};
export default objectUnderTest;
objectUnderTest.myFunctionUnterTest = () => {
return "this is original function";
};
objectUndertest.test.js
import _ from "lodash";
import objectUndertest from "./objectUndertest.js";
describe("objectUndertest", () => {
let mockObject = objectUndertest;
beforeEach(() => {
mockObject = _.cloneDeep(objectUndertest);
});
test("test function", () => {
mockObject.myFunctionUnterTest = () => {
return "this is mocked function.";
};
expect(mockObject.myFunctionUnterTest()).toBe("this is mocked function.");
});
});

Related

Testing effects NgRx Angular 11

I am having problems while trying to test effects in my Angular 11 project. I use the karma runner. It would seem that when I dispatch an action in my tests, the effects does't seem to respond to it.
import { TestBed } from '#angular/core/testing';
import { of } from 'rxjs';
import { provideMockActions } from '#ngrx/effects/testing';
import {
SimpleActionTypes,
} from '../actions/simple.action';
import {SimpleEffects} from './simple.effect';
import {MockStore, provideMockStore} from '#ngrx/store/testing';
import {Store} from '#ngrx/store';
describe('SimpleEffects', () => {
let actions$: any;
let effects: SimpleEffects;
let store: MockStore<Store>;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
SimpleEffects,
provideMockActions(() => actions$),
provideMockStore() ,
],
});
store = TestBed.inject(MockStore);
effects = TestBed.inject(SimpleEffects);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
it('should fire with a default price', (done) => {
// Mock the action that we use to activate the effect
actions$ = of(SimpleActionTypes.UnavailablePrice);
// Subscribe to the effect
effects.getPriceAfterLocalCartUpdate.subscribe((res) => {
// the expected results verification
expect(res).toEqual(SimpleActionTypes.ComputePrice);
// And all done !
done();
});
});
});
I have tried many ways to enter the subscribe part of my effect (using marbles hot cold ...), but it doesn't seem to work. I have a failure indicating :
"SimpleEffects should fire with a default price FAILED
Error: Timeout - Async function did not complete within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)"
The micro project is hosted here : https://github.com/Ushelajyan/simple-ngrx-testing-effects
Thank you in advance for your help.
You override actions$ within your test (it-block). Unfortunately the TestBed.configureTestingModule() gets executed inside a beforeEach block, which happens right before the it-block gets executed.
import { TestBed } from '#angular/core/testing';
import { of } from 'rxjs';
import { provideMockActions } from '#ngrx/effects/testing';
import {
SimpleActionTypes,
} from '../actions/simple.action';
import {SimpleEffects} from './simple.effect';
import {MockStore, provideMockStore} from '#ngrx/store/testing';
import {Store} from '#ngrx/store';
describe('SimpleEffects', () => {
let store: MockStore<Store>;
const createEffects = (actions$) => {
TestBed.configureTestingModule({
providers: [
SimpleEffects,
provideMockActions(() => actions$),
provideMockStore() ,
],
});
store = TestBed.inject(MockStore);
return TestBed.inject(SimpleEffects);
};
it('should be created', () => {
const effects = createEffects(of(undefined));
expect(effects).toBeTruthy();
});
it('should fire with a default price', (done) => {
// Mock the action that we use to activate the effect
const actions$ = of(SimpleActionTypes.UnavailablePrice);
// Create the effect with your given mock
const effects = createEffects(actions$)
effects.getPriceAfterLocalCartUpdate.subscribe((res) => {
// the expected results verification
expect(res).toEqual(SimpleActionTypes.ComputePrice);
// And all done !
done();
});
});
});
This should do the trick.
I added a createEffect function that configures your TestBed properly with the given actions$ mock and returns a new instance of SimpleEffects.
This instance can then be used to subscribe to its defined effects.

TypeError: Webpack imported module is not a function

I have a backend that calculates work shifts.
I am trying to post some required user input with a module in services/shifts.
The getAll method works fine, but posting throws an error
TypeError: _services_shifts__WEBPACK_IMPORTED_MODULE_2__.default.postData is not a function
Shiftservice module:
import axios from 'axios'
const baseUrl = '...'
const getAll = () => {
const request = axios.get(baseUrl)
return request.then(response => response.data)
}
const postData = newObject => {
const request = axios.post(baseUrl, newObject)
return request.then(response => response.data)
}
export default {getAll, postData}
I have a button that triggers the following calling code on click:
import shiftService from './services/shifts'
const postData = (event) => {
event.preventDefault()
const sampleObject = {
sampleField: sample
}
shiftService
.postData(sampleObject)
.then(returnedData => {
console.log(returnedData)
})
}
When execution reaches shiftService.postData, the error is thrown.
I am really confused since I am basically copying some older project of mine which works, but here I just don't find the problem. Thank you in advance for helping a newcomer!
Modules provide special export default (“the default export”) syntax to make the “one thing per module” way look better.There may be only one export default per file.And we may neglect the name of the class in the following example.
//Module1
export default class{
}
And then import it without curly braces with any name:
//Module2
import anyname from './Module1'
Your scenario is different having two functions.You can either export default one function
export default getAll
and normal export the other function.
export postData
and when importing
import{ default as getAll,postData} from './yourModule'
OR
Remove default here in Shiftservice module and export normally:
import axios from 'axios'
const baseUrl = '...'
const getAll = () => {
const request = axios.get(baseUrl)
return request.then(response => response.data)
}
const postData = newObject => {
const request = axios.post(baseUrl, newObject)
return request.then(response => response.data)
}
export {getAll, postData}
Importing in your module
import {getAll,PostData} from './Module1'
or
import * as shiftService from './Module1'
and then use shiftServer.postData().....
Okay, I am embarrassed of the solution. I was just editing an earlier version of shiftService from a wrong folder, and the imported service only had the get method in it...
So my code actually works if placed correctly. Thank you for your time, and thanks for sharing alternative ways that must work aswell.
I think its becuse your declaring the function as arrow functions
and export it this way:
export default {getAll, postData}
you need to declare them as a normal functions
function postData(){
}
that should work

Test with toJson and without it

import React from 'react'
import toJson from 'enzyme-to-json'
import {ScreensCreateAccount} from './CreateAccount'
describe('Testing CreateAccount Component', () => {
const props = {
auth: {
isAuth: false,
isLoadding: false
}
}
it('should render correctly', () => {
const wrapper = shallow(<ScreensCreateAccount {...props}/>)
expect(toJson(wrapper)).toMatchSnapshot()
})
})
I have this test for my component CreateAccount, and i want to know if is necessary to use expect(toJson(wrapper)).toMatchSnapshot() or using just
expect(wrapper).toMatchSnapshot() is correct too. Both implementation build a folder with snapshots with file CreateAccount.test.js.snap. Whats is the diference?
Enzyme-to-json serializes component, created by Enzyme. If your tests work without it, then you have it already configured in jest configuration file.
If no, you can add this to package.json to work:
"jest": {
"snapshotSerializers": ["enzyme-to-json/serializer"]
}

Jest: Mock ES6 Module with both default and named export

I have an ES6 module with both a default and a named export:
/** /src/dependency.js **/
export function utilityFunction() { return false; }
export default function mainFunction() { return 'foo'; }
Its being used by a second ES6 module:
/** /src/myModule.js **/
import mainFunction, { utilityFunction } from './dependency';
// EDIT: Fixed syntax error in code sample
// export default myModule() {
export default function myModule() {
if (!utilityFunction()) return 2;
return mainFunction();
}
I'm trying to write a unit test for myModule.js using Jest. But when I try to mock both the named and the default import, Jest appears to only mock the named import. It continues to use the actual implementation of the default import, and doesn't allow me to mock it, even after I call .mockImplementation(). Here's the code I'm trying to use:
/**
* Trying to mock both named and default import.
* THIS DOESN'T WORK.
*/
/** /tests/myModule.test.js **/
import mainFunction, { utilityFunction } from '../src/dependency';
import myModule from '../src/myModule';
jest.mock('../src/dependency');
mainFunction.mockImplementation(() => 1);
utilityFunction.mockImplementation(() => true);
describe('myModule', () => {
it('should return the return value of mainFunction when the result of utilityFunction is true', () => {
expect(myModule()).toEqual(1); // FAILS - actual result is 'foo'
});
});
This behavior seems really strange to me, because when mocking JUST default imports or JUST named imports, this API works fine. For example, in the case where myModule.js only imports a default import, this is pretty easily done:
/**
* Trying to mock just the default import.
* THIS WORKS.
*/
/** /src/myModule.js **/
import mainFunction from './dependency';
// EDIT: Fixed syntax error in code sample
// export default myModule() {
export default function myModule() {
return mainFunction();
}
/** /tests/myModule.test.js **/
// If only mainFunction is used by myModule.js
import mainFunction from '../src/dependency';
import myModule from '../src/myModule';
jest.mock('../src/dependency');
mainFunction.mockImplementation(() => 1);
describe('myModule', () => {
it('should return the return value of mainFunction', () => {
expect(myModule()).toEqual(1); // Passes
});
});
In the case where only the named 'utilityFunction' export is used, its also pretty easy to mock the import:
/**
* Trying to mock both named and default import.
* THIS WORKS.
*/
/** /src/myModule.js **/
import { utililtyFunction } from './dependency';
// EDIT: Fixed syntax error in code sample
// export default myModule()
export default function myModule() {
return utilityFunction();
}
/** /tests/myModule.test.js **/
// If only utilityFunction is used by myModule.js
import { utilityFunction } from '../src/dependency';
import myModule from '../src/myModule';
jest.mock('../src/dependency');
utilityFunction.mockImplementation(() => 'bar');
describe('myModule', () => {
it('should return the return value of utilityFunction', () => {
expect(myModule()).toEqual('bar'); // Passes
});
});
Is it possible to mock both the named and default import using Jest? Is there a different syntax that I can use to achieve the result I want, where I import both named and default values from a module and am able to mock them both?
The other solution didn't work for me. This is how I did:
jest.mock('../src/dependency', () => ({
__esModule: true,
utilityFunction: 'utilityFunction',
default: 'mainFunction'
}));
Another way to do it:
jest.unmock('../src/dependency');
const myModule = require('../src/dependency');
myModule.utilityFunction = 'your mock'
You have a syntax error ... the function keyword is omitted from the default export in myModule.js. Should look like this:
import mainFunction, { utilityFunction } from './dependency';
export default function myModule() {
if (!utilityFunction()) return 2;
return mainFunction();
}
I'm not sure how you got the test to run otherwise, but I just tried this out locally and it passed.

Testing native event emitters and natives modules in React Native

I have a React Native component which communicates with a custom iOS class, so I make use of NativeModules en NativeEventEmitter to send commands to and receive commands from the native code.
import {NativeModules, NativeEventEmitter} from 'react-native';
/* inside the constructor I do some setup: */
const { NetworkManager } = NativeModules;
const emitter = new NativeEventEmitter(NetworkManager);
/* here I subscribe to an event from the emitter */
public startDiscovery() {
const deviceFoundSubscription = this._emitter.addListener(
"DeviceDiscovered",
(device) => this.deviceFound(device)
);
this.NetworkManager.startDiscovery();
}
This code works just fine, but now I wanted to write some tests with Jest and this is where I am stuck. How would I go ahead to write a test for the event listener? I want to simulate a DeviceDiscovered event in a Jest test, and then assert that the listener is called.
To solve my problem I have mocked RCTDeviceEventEmitter by using an everyday JS EventEmitter:
const EventEmitter = require('EventEmitter');
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
/**
* Mock the NativeEventEmitter as a normal JS EventEmitter.
*/
export class NativeEventEmitter extends EventEmitter {
constructor() {
super(RCTDeviceEventEmitter.sharedSubscriber);
}
}
Than in my setupFile for jest I imported the mock to replace the react-native implementation.
import NativeEventEmitter from './__mocks__/nativeEventEmitter';
import { NativeModules } from 'react-native';
// Mock for my native module where I create a spy for the methods
const mockNativeModules = {
NetworkManager: {
startDiscovery: jest.fn(),
stopDiscovery: jest.fn(),
connectToHost: jest.fn(),
sendMessage: jest.fn()
}
};
// Mock every native module you use
Object.keys(mockNativeModules).forEach((module => {
jest.doMock(module, () => mockNativeModules[module], { virtual:true });
}));
jest.doMock('NativeModules', () => mockNativeModules);
jest.mock('NativeEventEmitter');
And than finally my actual test code:
import {
NativeModules,
NativeEventEmitter,
} from 'react-native';
import { DiscoveryService } from '/services/discoveryService';
import { device1, device2 } from './../fixtures/devices';
const nativeEventEmitter = new NativeEventEmitter();
describe('Discovery Service', () => {
beforeEach(() => {
discoveryService.deviceDiscovered = jest.fn();
discoveryService.startDiscovery();
});
test('Should call startDiscovery on Native Module NetworkManager', () => {
nativeEventEmitter.emit('DeviceDiscovered', device);
expect(NativeModules.NetworkManager.startDiscovery).toHaveBeenCalledTimes(1);
expect(discoveryService.serverFound).toHaveBeenCalledWith(device1);
});
test('Should handle multiple discoveries', () => {
nativeEventEmitter.emit('DeviceDiscovered', device1);
expect(discoveryService.serverFound).toHaveBeenCalledWith(device1);
nativeEventEmitter.emit('DeviceDiscovered', device2)
expect(discoveryService.deviceFound).toHaveBeenCalledWith(device2);
});
});
in your jest/setup.js
add your relative path:
jest.mock(
'../node_modules/react-native/Libraries/EventEmitter/NativeEventEmitter',
);
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter.js', () => {
const { EventEmitter } = require('events');
return EventEmitter;
});

Categories

Resources