I am trying to mock NativeModules from react-native, but I can't find a way to only mock that class, and not the entire react-native module.
Basically, in my production code I do this:
import { NativeModules } from 'react-native'
const { MyCustomNativeModule } = NativeModules
In my tests, I want to rewrite MyCustomNativeModule. At the moment the only way I have found is to mock the entire react-native module like this:
// /__mocks__/react-native.js
module.exports = {
NativeModules: {
MyCustomNativeModule: {
dismiss: () => {},
},
},
}
But that breaks all the other react-native functions. I saw that often people use methods like jest.mock('NativeModules', () => ... ) but that really doesn't seem to be working!
Here is the solution using jest.mock to mock react-native module manually.
In order to keep it simple, I simulate react-native module. You can use real react-native to replace the simulated one.
The file structure is:
.
├── index.spec.ts
├── index.ts
└── react-native.ts
Simulated react-native module:
react-native.ts:
const NativeModules = {
MyCustomNativeModule: {
dismiss: () => {
// original implementation
return 'real data';
}
}
};
export { NativeModules };
index.ts, assume you import and use react-native module in this file:
import { NativeModules } from './react-native';
export function main() {
return NativeModules.MyCustomNativeModule.dismiss();
}
Unit test, index.spec.ts:
import { main } from './';
import { NativeModules } from './react-native';
jest.mock('./react-native', () => {
return {
NativeModules: {
MyCustomNativeModule: {
dismiss: jest.fn()
}
}
};
});
describe('main', () => {
it('should mock react-native correctly', () => {
const mockedData = 'mocked data';
(NativeModules.MyCustomNativeModule.dismiss as jest.MockedFunction<
typeof NativeModules.MyCustomNativeModule.dismiss
>).mockReturnValueOnce(mockedData);
const actualValue = main();
expect(actualValue).toBe(mockedData);
expect(NativeModules.MyCustomNativeModule.dismiss).toBeCalledTimes(1);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/54393006/index.spec.ts
main
✓ should mock react-native correctly (19ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.096s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/54393006
what about this: https://jestjs.io/docs/en/es6-class-mocks
class
// sound-player.js
export default class SoundPlayer {
constructor() {
this.foo = 'bar';
}
playSoundFile(fileName) {
console.log('Playing sound file ' + fileName);
}
}
mocking with jest.mock( )
import SoundPlayer from './sound-player';
const mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};
});
});
UPDATE
What about this way:
function mockFunctions() {
const original = require.requireActual('../myModule');
return {
...original, //Pass down all the exported objects
test: jest.fn(() => {console.log('I didnt call the original')}),
someFnIWantToCurry: {console.log('I will curry the original') return jest.fn((...args) => original.someFnIWantToCurry(...args)}),
}
jest.mock('../myModule', () => mockFunctions());
const storage = require.requireMock('../myModule');
see: https://github.com/facebook/jest/issues/936#issuecomment-265074320
Related
I am having a Typescript backend structure and I want to create unit tests for all the functionalities. I am using JEST and aws-skd-mock for mocking AWS. I have tried some things but it seems I am not doing the right thing.
I have this service where I am getting a parameter from ParamterStore (amazon.service.ts):
import * as AWS from "aws-sdk";
export class AmazonService {
parameterStore: AWS.SSM;
constructor() {
this.parameterStore = new AWS.SSM();
}
async getParam(param) {
let self = this;
console.log('IN getPARAM', param);
return new Promise(function(resolve, reject){
self.parameterStore.getParameter({
Name: param,
WithDecryption: true
}, function (err, data) {
if (err) {
console.log('Error ', err);
return resolve({Error: 'ParameterNotFound'})
}
console.log('RES ', data.Parameter.Value);
return resolve(data.Parameter.Value)
})
})
}
}
Then, I mock whole amazon.service file, I mock SSM.getParameter with response in my test file (amazon.service.spect.ts):
import * as AWSMock from "aws-sdk-mock";
import * as AWS from "aws-sdk";
import {AmazonService} from "./amazon.service";
jest.mock('./amazon.service');
describe('amazon service mock', () => {
let amazonService: AmazonService;
it('should get Parameter from Parameter Store', async () => {
const ssmGetParameterPromise = jest.fn().mockReturnValue({
promise: jest.fn().mockResolvedValue({
Parameter: {
Name: 'NAME',
Type: 'SecureString',
Value: 'VALUE',
Version: 1,
LastModifiedDate: 1546551668.495,
ARN: 'arn:aws:ssm:eu-test-1:123:NAME'
}
})
});
AWSMock.setSDKInstance(AWS);
AWSMock.mock('SSM', 'GetParameter', ssmGetParameterPromise);
amazonService = new AmazonService();
console.log(await amazonService.getParam('NAME'))
await expect(amazonService.getParam('NAME')).resolves.toBe('VALUE')
})
});
With this I get undefined when amazonService.getParam is called.
As I looked in the examples they are initializing new AWS.SSM() right after is mocked and call it from test, but I want to achieve that by calling my function. It seems like SSM is not mocked when my function is called.
Any suggestions how to do this right ?
You don't need to mock ./amazon.service.ts module. Here is the unit test solution without using aws-sdk-mock.
E.g.
amazon.service.ts:
import * as AWS from 'aws-sdk';
export class AmazonService {
parameterStore: AWS.SSM;
constructor() {
this.parameterStore = new AWS.SSM();
}
async getParam(param) {
let self = this;
console.log('IN getPARAM', param);
return new Promise(function (resolve, reject) {
self.parameterStore.getParameter(
{
Name: param,
WithDecryption: true,
},
function (err, data) {
if (err) {
console.log('Error ', err);
return resolve({ Error: 'ParameterNotFound' });
}
console.log('RES ', data.Parameter!.Value);
return resolve(data.Parameter!.Value);
},
);
});
}
}
amazon.service.spec.ts:
import * as AWS from 'aws-sdk';
import { AmazonService } from './amazon.service';
import { mocked } from 'ts-jest/utils';
import { AWSError } from 'aws-sdk';
import { GetParameterResult } from 'aws-sdk/clients/ssm';
jest.mock('aws-sdk', () => {
const mSSMInstance = {
getParameter: jest.fn(),
};
const mSSM = jest.fn(() => mSSMInstance);
return { SSM: mSSM };
});
describe('amazon service mock', () => {
let amazonService: AmazonService;
it('should get Parameter from Parameter Store', async () => {
amazonService = new AmazonService();
expect(AWS.SSM).toBeCalled();
const mSSMInstance = new AWS.SSM();
const mData = {
Parameter: {
Name: 'NAME',
Type: 'SecureString',
Value: 'VALUE',
Version: 1,
LastModifiedDate: new Date(1995, 11, 17),
ARN: 'arn:aws:ssm:eu-test-1:123:NAME',
},
};
mocked(mSSMInstance.getParameter).mockImplementationOnce(
(params, callback?: (err: AWSError | null, data: GetParameterResult) => void): any => {
if (callback) {
callback(null, mData);
}
},
);
const actual = await amazonService.getParam('NAME');
expect(actual).toBe('VALUE');
});
});
unit test result with coverage report:
PASS stackoverflow/61871955/amazon.service.spec.ts (9.613s)
amazon service mock
✓ should get Parameter from Parameter Store (19ms)
console.log
IN getPARAM NAME
at AmazonService.<anonymous> (stackoverflow/61871955/amazon.service.ts:12:13)
console.log
RES VALUE
at stackoverflow/61871955/amazon.service.ts:24:19
-------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------------|---------|----------|---------|---------|-------------------
All files | 86.67 | 50 | 100 | 85.71 |
amazon.service.ts | 86.67 | 50 | 100 | 85.71 | 21-22
-------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.925s
I have the following code in javascript:
class SampleClass {
constructor(param1) {
this.param1 = param1;
this.init();
}
async init() {
await this.getData();
this.loadIframe();
}
async getData() {
const response = fetch(url);
const data = response.json();
//set the response to class variable
this.param2 = data;
}
loadIframe() {
//some logic to load iframe.
}
}
What would be the best approach to test this using jest?
Currently I am testing the constructor logic by mocking the init() function.
But I also have to test the getData() function. What should be the approach to test the getData() method.
I tried testing the getData() function by not mocking the init() function and using async testing, but I am not sure where to use the await in the test as the function is nested and called from within the init which is called from constructor.
it('should fetch data', async()=>{
//some logic
})
You can use jest.spyOn(object, methodName) to mock your methods of the SampleClass. My test environment is node, so I mock fetch method on global object. If your test environment is browser, the fetch method is on window object.
E.g.
sampleClass.js:
class SampleClass {
constructor(param1) {
this.param1 = param1;
this.init();
}
async init() {
await this.getData();
this.loadIframe();
}
async getData() {
const url = 'https://stackoverflow.com/';
const response = fetch(url);
const data = response.json();
this.param2 = data;
}
loadIframe() {}
}
export { SampleClass };
sampleClass.test.js:
import { SampleClass } from './sampleClass';
describe('60146073', () => {
afterEach(() => {
jest.restoreAllMocks();
});
describe('#constructor', () => {
it('should consturt', () => {
jest.spyOn(SampleClass.prototype, 'constructor');
jest.spyOn(SampleClass.prototype, 'init').mockReturnValueOnce();
const instance = new SampleClass('param1');
expect(instance.param1).toBe('param1');
expect(instance.init).toBeCalledTimes(1);
});
});
describe('#init', () => {
it('should init', async () => {
jest.spyOn(SampleClass.prototype, 'getData').mockResolvedValueOnce();
jest.spyOn(SampleClass.prototype, 'loadIframe').mockReturnValueOnce();
jest.spyOn(SampleClass.prototype, 'init').mockReturnValueOnce();
const instance = new SampleClass();
await instance.init();
expect(instance.getData).toBeCalledTimes(1);
expect(instance.loadIframe).toBeCalledTimes(1);
});
});
describe('#getData', () => {
it('should fetch data', async () => {
const mResponse = { json: jest.fn().mockReturnValueOnce({}) };
global.fetch = jest.fn().mockResolvedValueOnce(mResponse);
jest.spyOn(SampleClass.prototype, 'init').mockReturnValueOnce();
const instance = new SampleClass();
await instance.getData();
expect(global.fetch).toBeCalledWith('https://stackoverflow.com/');
expect(mResponse.json).toBeCalledTimes(1);
});
});
});
Unit test results with 100% coverage:
PASS stackoverflow/60146073/sampleClass.test.js
60146073
#constructor
✓ should consturt (3ms)
#init
✓ should init (2ms)
#getData
✓ should fetch data (2ms)
----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 80 | 100 |
sampleClass.js | 100 | 100 | 80 | 100 |
----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 4.137s, estimated 5s
Source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60146073
Update January 22nd 2020
The solution from #slideshowp2 is correct, but I could not get it to work at all, due to this TypeError:
TypeError: Cannot read property 'query' of undefined
Well it turned out to be my jest configuration that had resetMocks: true set. After I removed it, the test did pass. (I don't know why though)
Original question:
I need to execute a graphql query in a helper function outside of a React component using Apollo Client and after a bit of trial and error I went for this approach which is working as it is supposed to:
setup.ts
export const setupApi = (): ApolloClient<any> => {
setupServiceApi(API_CONFIG)
return createServiceApolloClient({ uri: `${API_HOST}${API_PATH}` })
}
getAssetIdFromService.ts
import { setupApi } from '../api/setup'
const client = setupApi()
export const GET_ASSET_ID = gql`
query getAssetByExternalId($externalId: String!) {
assetId: getAssetId(externalId: $externalId) {
id
}
}
`
export const getAssetIdFromService = async (externalId: string) => {
return await client.query({
query: GET_ASSET_ID,
variables: { externalId },
})
return { data, errors, loading }
}
Now I am trying to write test tests for the getAssetIdFromService function, but I have trouble figuring out how to get the client.query method to work in tests.
I have tried the approach below including many others that did not work.
For this particular setup, jest throws
TypeError: client.query is not a function
import { setupApi } from '../../api/setup'
import { getAssetIdFromService } from '../getAssetIdFromService'
jest.mock('../../api/setup', () => ({
setupApi: () => jest.fn(),
}))
describe('getAssetIdFromService', () => {
it('returns an assetId when passed an externalId and the asset exists in the service', async () => {
const { data, errors, loading } = await getAssetIdFromService('e1')
// Do assertions
})
}
I assume I am missing something in relation to this part:
jest.mock('../../api/setup', () => ({
setupApi: () => jest.fn(),
}))
...but I cannot see it.
You didn't mock correctly. Here is the correct way:
getAssetIdFromService.ts:
import { setupApi } from './setup';
import { gql } from 'apollo-server';
const client = setupApi();
export const GET_ASSET_ID = gql`
query getAssetByExternalId($externalId: String!) {
assetId: getAssetId(externalId: $externalId) {
id
}
}
`;
export const getAssetIdFromService = async (externalId: string) => {
return await client.query({
query: GET_ASSET_ID,
variables: { externalId },
});
};
setup.ts:
export const setupApi = (): any => {};
getAssetIdFromService.test.ts:
import { getAssetIdFromService, GET_ASSET_ID } from './getAssetIdFromService';
import { setupApi } from './setup';
jest.mock('./setup.ts', () => {
const mApolloClient = { query: jest.fn() };
return { setupApi: jest.fn(() => mApolloClient) };
});
describe('59829676', () => {
it('should query and return data', async () => {
const client = setupApi();
const mGraphQLResponse = { data: {}, loading: false, errors: [] };
client.query.mockResolvedValueOnce(mGraphQLResponse);
const { data, loading, errors } = await getAssetIdFromService('e1');
expect(client.query).toBeCalledWith({ query: GET_ASSET_ID, variables: { externalId: 'e1' } });
expect(data).toEqual({});
expect(loading).toBeFalsy();
expect(errors).toEqual([]);
});
});
Unit test results with 100% coverage:
PASS apollo-graphql-tutorial src/stackoverflow/59829676/getAssetIdFromService.test.ts (8.161s)
59829676
✓ should query and return data (7ms)
--------------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
getAssetIdFromService.ts | 100 | 100 | 100 | 100 | |
--------------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.479s
Source code: https://github.com/mrdulin/apollo-graphql-tutorial/tree/master/src/stackoverflow/59829676
Use blow mock class:
class ApolloClient {
constructor(uri: string, fetch: any, request: any) {}
setupApi() {
return {
query: jest.fn(),
};
}
query() {
return jest.fn();
}
}
module.exports = ApolloClient;
and add below line to jest.cofig.ts
moduleNameMapper: {
'apollo-boost': '<rootDir>/.jest/appolo-client.ts',
},
I have the following method in a react app service which requires Unit Testing.
onDeclineCallback = () => {
console.log("boom " + document.referrer);
if (document.referrer === "") {
console.log("redirect to our age policy page");
} else {
history.back();
}
};
My unit test currently looks like:
history.back = jest.fn(); // Mocking history.back as jest fn
describe('age verifiction service test', () => {
it('returns user to referrer if declined and referrer is available', () => {
document = {
...document,
referrer: 'Refferer Test', // My hacky attempt at mocking the entire document object
};
ageVerification.onDeclineCallback();
expect(history.back).toHaveBeenCalledTimes(1);
});
});
I am trying to find a way of mocking document.referrer in order to write a unit test for each case. Can anyone provide an approach for this?
You can use Object.defineProperty method to set a mocked value of document.referrer.
E.g.
index.ts:
export class AgeVerification {
public onDeclineCallback = () => {
console.log('boom ' + document.referrer);
if (document.referrer === '') {
console.log('redirect to our age policy page');
} else {
history.back();
}
};
}
index.spec.ts:
import { AgeVerification } from './';
describe('age verifiction service test', () => {
let ageVerification;
beforeEach(() => {
ageVerification = new AgeVerification();
history.back = jest.fn();
});
afterAll(() => {
jest.restoreAllMocks();
jest.resetAllMocks();
});
it('returns user to referrer if declined and referrer is available', () => {
const originalReferrer = document.referrer;
Object.defineProperty(document, 'referrer', { value: 'Refferer Test', configurable: true });
ageVerification.onDeclineCallback();
expect(history.back).toHaveBeenCalledTimes(1);
Object.defineProperty(document, 'referrer', { value: originalReferrer });
});
it('should print log', () => {
const logSpy = jest.spyOn(console, 'log');
ageVerification.onDeclineCallback();
expect(logSpy.mock.calls[0]).toEqual(['boom ']);
expect(logSpy.mock.calls[1]).toEqual(['redirect to our age policy page']);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/59198002/index.test.ts (13.915s)
age verifiction service test
✓ returns user to referrer if declined and referrer is available (17ms)
✓ should print log (3ms)
console.log src/stackoverflow/59198002/index.ts:264
boom Refferer Test
console.log node_modules/jest-mock/build/index.js:860
boom
console.log node_modules/jest-mock/build/index.js:860
redirect to our age policy page
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 15.385s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59198002
worked for me, the jest mock way:
jest.spyOn(document, 'referrer', 'get').mockReturnValue('mock ref value');
I understand reaching 100% unit test coverage shouldn't be a goal. I have actually excluded index.js from the coverage tests, I'm just interested to know how it would be done.
How can the self-executing function in index.js be tested? I'd like to test that it calls scrape.
index.js:
import scrape from './scrape';
const main = (async () => {
await scrape();
})();
export default main;
What I have tried:
import main from './index';
const scrape = jest.fn();
describe('On the index file', () => {
it('scrape executes automatically', async () => {
await main();
expect(scrape).toHaveBeenCalled();
});
});
This errors with TypeError: (0 , _index.default) is not a function
I see that main isn't a function but I haven't managed to make it work.
Here is my test strategy, you can use jest.mock(moduleName, factory, options) method mock the scrape module.
index.ts:
import scrape from './scrape';
const main = (async () => {
return await scrape();
})();
export default main;
index.spec.ts:
import scrape from './scrape';
import main from './';
jest.mock('./scrape.ts', () => jest.fn().mockResolvedValueOnce('fake data'));
describe('main', () => {
test('should return data', async () => {
const actualValue = await main;
expect(actualValue).toBe('fake data');
expect(scrape).toBeCalledTimes(1);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/58674975/index.spec.ts (7.434s)
main
✓ should return data (5ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.684s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58674975