I am using the testing-library/jest-dom + Enzyme to do the unit testing.and here I need to test a funtion inside a React function.
import React, { useEffect } from 'react';
function Component1() {
useEffect( async () => {
fetchSomeData();
}, [])
const fetchSomeData = async () =>{
console.log('fetchSomeData')
}
return <div>Component1</div>;
}
export default Component1;
and my test file:
Enzyme.configure({adapter:new Adapter()})
describe('Name of the group', () => {
it('should ', () => {
const wrapper = shallow( <Component1/>)
expect(wrapper.exists());
});
});
in fact I don't need to test the correctness , I just need the code be covered in jest. but whatever I tried , it seems not covered by jest:
Related
I'm just trying to get the ES6 Class Mocks example provided by Jest to run green.
here's my code repo
it's taken me way to long to even get to this point, but the tests still fail with
TypeError: SoundPlayer.mockClear is not a function
system under test
import SoundPlayer from './sound-player';
export default class SoundPlayerConsumer {
constructor() {
this.soundPlayer = new SoundPlayer();
}
playSomethingCool() {
const coolSoundFileName = 'song.mp3';
this.soundPlayer.playSoundFile(coolSoundFileName);
}
}
the test
import {jest} from '#jest/globals';
import SoundPlayer from './sound-player';
import SoundPlayerConsumer from './sound-player-consumer';
const mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};
});
});
beforeEach(() => {
SoundPlayer.mockClear();
mockPlaySoundFile.mockClear();
});
it('The consumer should be able to call new() on SoundPlayer', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
// Ensure constructor created the object:
expect(soundPlayerConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
expect(SoundPlayer).toHaveBeenCalledTimes(1);
});
it('We can check if the consumer called a method on the class instance', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
const coolSoundFileName = 'song.mp3';
soundPlayerConsumer.playSomethingCool();
expect(mockPlaySoundFile.mock.calls[0][0]).toEqual(coolSoundFileName);
});
system under test dependency
export default class SoundPlayer {
constructor() {
this.foo = 'bar';
}
playSoundFile(fileName) {
console.log('Playing sound file ' + fileName);
}
}
I have one file called helper.js that consist of two functions
export const funcA = (key) => {
return funcB(key)
};
export const funcB = (key,prop) => {
return someObj;
};
I have my helper.spec.js to test the helper.js file functions.
import {funcA,funcB} from 'helper';
describe('helper', () => {
test('testFuncB', () => {
}
test('testFuncA', () => {
}
}
The test for funcB is pretty simple i just call it and expect someObj
The problem is to test funcA, in order to test it i want to mock the response of funcB.
I want testFuncB call the actual funcB and testFuncA call mocked funcB
How can i achieve funcB to be mocked and original in my two tests?
This is not a duplicate. It is a different case: they mock inner called functions only, if I remove the testFuncB then it will be the same but I must perform test on testFuncB too.
If an ES6 module directly exports two functions (not within a class, object, etc., just directly exports the functions like in the question) and one directly calls the other, then that call cannot be mocked.
In this case, funcB cannot be mocked within funcA the way the code is currently written.
A mock replaces the module export for funcB, but funcA doesn't call the module export for funcB, it just calls funcB directly.
Mocking funcB within funcA requires that funcA call the module export for funcB.
That can be done in one of two ways:
Move funcB to its own module
funcB.js
export const funcB = () => {
return 'original';
};
helper.js
import { funcB } from './funcB';
export const funcA = () => {
return funcB();
};
helper.spec.js
import * as funcBModule from './funcB';
import { funcA } from './helper';
describe('helper', () => {
test('test funcB', () => {
expect(funcBModule.funcB()).toBe('original'); // Success!
});
test('test funcA', () => {
const spy = jest.spyOn(funcBModule, 'funcB');
spy.mockReturnValue('mocked');
expect(funcA()).toBe('mocked'); // Success!
spy.mockRestore();
});
});
Import the module into itself
"ES6 modules support cyclic dependencies automatically" so it is perfectly valid to import a module into itself so that functions within the module can call the module export for other functions in the module:
helper.js
import * as helper from './helper';
export const funcA = () => {
return helper.funcB();
};
export const funcB = () => {
return 'original';
};
helper.spec.js
import * as helper from './helper';
describe('helper', () => {
test('test funcB', () => {
expect(helper.funcB()).toBe('original'); // Success!
});
test('test funcA', () => {
const spy = jest.spyOn(helper, 'funcB');
spy.mockReturnValue('mocked');
expect(helper.funcA()).toBe('mocked'); // Success!
spy.mockRestore();
});
});
Late answer but this should work.
Also you should test funcB in its own file and not inside the 'helper' tests.
import { funcB } from './funcB';
import { funcA } from './helper';
jest.mock('./funcB');
describe('helper', () => {
test('test funcA', () => {
const funcBSpy = jest.fn();
funcB.mockImplementation(() => funcBSpy());
funcA();
expect(funcBSpy).toHaveBeenCalledTimes(1);
});
});
import * as helper from 'helper';
describe('helper', () => {
it('should test testFuncA', () => {
const mockTestFuncB = jest.mock();
// spy on calls to testFuncB and respond with a mock function
mockTestFuncB.spyOn(helper, 'testFuncB').mockReturnValue(/*your expected return value*/);
// test logic
// Restore helper.testFuncB to it's original function
helper.testFuncB.mockRestore();
}
}
I create a kind of nameSpace to handle this issue:
let helper = {}
const funcA = (key) => {
return helper.funcB(key)
};
const funcB = (key,prop) => {
return someObj;
};
helper = { funcA, funcB }
module.exports = helper
and then mocking is obvious with jest.fn
You can use babel-plugin-rewire provided __set__ function to mock internal function.
Assuming you have set up babel-plugin-rewire.
helper.spec.js
import {funcA, __set__} as helper from './helper';
describe('helper', () => {
test('test funcA', () => {
__set__('funcB', () => {
return 'funcB return value'
})
expect(funcA()).toBe('funcB return value');
});
});
One advantage of this solution is you don't need to change any original code
I was able to get this working. I separated my helper and my main logic into two files like other solutions. In the test file, I had to mock the entire helper file.
const { doAdd } = require('./addHelper');
function add(a, b) {
return doAdd(a, b);
}
jest.mock('./addHelper');
// ...
it('should call doAdd', () => {
// hook into doAdd helper method and intercept its return value
jest.spyOn(helperModule, 'doAdd').mockReturnValue(11);
expect(addModule.add()).toEqual(11);
expect(helperModule.doAdd).toBeCalled();
});
Here is my solution:
https://github.com/davidholyko/jest-sandbox
You can do the following trick when you test the funcA:
1.Mock the funcB:
helper.funcB = jest.fn().mockImplementationOnce(() => <your data>);
2.Change the funcB(key) to this.funcB(key)
I had the same problem and worked! Full Code:
export const funcA = (key) => {
return this.funcB(key)
};
export const funcB = (key,prop) => {
return someObj;
};
Test Code:
import helper from 'helper';
describe('helper', () => {
test('testFuncB', () => {
...
}
test('testFuncA', () => {
helper.funcB = jest.fn().mockImplementationOnce(() => <your data>);
}
}
I think this might work
import * as helper from 'helper';
describe('helper', () => {
test('testFuncB', () => {
}
test('testFuncA', () => {
const mockTestFuncB = jest.mock();
// spy on calls to testFuncB and respond with a mock function
jest.spyOn(helper, 'testFuncB').mockImplementationOnce(mockTestFuncB);
// Do the testing ...
// Restore helper.testFuncB to it's original function
helper.testFuncB.mockRestore();
}
}
My react component should do some change to the state according to the current location.
There is custom hook, that is called upon component loading.
In the hook there is a check of useLocation().pathname and the result in switch/case for the correct change.
Can this be Jest tested in one file/describe?
I tried to jest.mock useLocation but I just can't do it inside Jest describe...
This is the mock, currently out of the describe - and this works, but can't be changed from test to test:
const mockUseLocation = () => {
jest.mock('react-router-dom', () => ({
useLocation: jest.fn().mockReturnValue({
pathname: '/val1'
})
}))
};
How can I test all the switch/case branches?
switch (pathname) {
case 'val1':
return 100;
case 'val2':
return 200;
...
...
}
To go off of #skyboyer answer I got this working well using renderHook
The first argument is your hook you want to test and the second argument is your wrapper which I wraps <MemoryRouter>, <Route> around children prop.
// test.js
import React, { FC } from 'react';
import { MemoryRouter, Route } from 'react-router-dom';
import { renderHook } from '#testing-library/react-hooks';
it('returns value from url query', () => {
const wrapper: FC = ({ children }) => (
<MemoryRouter initialEntries={[`/?user=authorized`]}>
<Route path="*" />
{children}
</MemoryRouter>
);
const { result } = renderHook(() => useLocation(), { wrapper });
expect(result.current).toBe('authorized');
});
easiest way is use MemoryRouter instead mocking useLocation. Also this would be helpful if your component has <Link>(otherwise you will get the error "Link cannot be used outside the Router"). Also it allows you to check if navigation happens. So with such many benefits mocking useLocation directly does not have any value.
Take a look into examples in official docs. It would be something alike:
test("current user is active in sidebar", () => {
const wrapper = mount(
<MemoryRouter initialEntries={["/users/2"]}>
<YourComp />
</MemoryRouter>
);
expected(wrapper.find(User)).toHaveLength(2);
});
You can change the value of pathname by changing the value of this variable in the appropriate places in the tests.
For example:
let mockPATH = 'val1';
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useLocation: () => ({
pathname: mockPATH
})
}));
test('1st', () => {
mockPATH = 'val2';
//...
})
try:
const locationMock = jest.fn();
const mockUseLocation = () => {
jest.mock('react-router-dom', () => ({
useLocation: () => locationMock()
}))
};
it('test1', () => {
locationMock.mockReturnValue({
pathname: '/val1'
})
expect()
})
it('test2', () => {
locationMock.mockReturnValue({
pathname: '/val2'
})
expect()
})
Hope it work for u ^^
jest.mock(..) does not seem to work at the 'describe' level for my tests.
If I have the following :
import React from 'react';
import {someFunction} from "./something/someFile";
describe('Overview Test', () => {
jest.mock(someFunction);
test(' snapshot', () => {
});
});
Then running the 'test' (ie. at the test level), works fine.
But if I run the 'describe' (ie. the describe level or suite level), then I get the following error :
TypeError: moduleName.split is not a function
at Resolver.resolveModuleFromDirIfExists (A:\frontend\node_modules\jest-resolve\build\index.js:224:30)
at Resolver.resolveModule (A:\frontend\node_modules\jest-resolve\build\index.js:252:12)
If I have this :
describe('Overview Test', () => {
test(' snapshot', () => {
jest.mock(someFunction);
});
});
Then both ways it does not work.
I have also tried this :
import React from 'react';
import {someFunction} from "./something/someFile";
describe('Overview Test', () => {
beforeEach(() => {
jest.mock(someFunction);
});
test(' snapshot', () => {
});
});
And it does not work.
UPDATE
I have also tried this and it does not work :
import React from 'react';
import {someFunction} from "./something/someFile";
describe('Overview Test', () => {
jest.mock('./something/someFile', () => {
return { someFunction: jest.fn(() => "futhissit")};
});
test(' snapshot', () => {
someFunction()
});
});
Jest mock is for mocking modules and the first argument is the moduleName which it has to be a valid module name (inside node_modules or a file path) and not a direct function/module:
jest.mock(moduleName, factory, options)
Mocks a module with an auto-mocked version when it is being required. factory and options are optional.
The error you're getting TypeError: moduleName.split is not a function is because resolveModuleFromDirIfExists tries to split the module name/path and you can see it inside jest-resolve/src/index.ts at line 207.
When you want to test an ES module, you pass the module location for the moduleName and you create a factory using __esModule: true and then create properties with exported functions being mocked using jest.fn():
someFile.js exports the someFunction:
module.exports.someFunction = () => 'Some function result!';
Mocking someFile.js module using jest.mock()
describe('Overview Test', () => {
// Mock the module and its functions
jest.mock('./someFile', () => ({
__esModule: true,
someFunction: jest.fn(() => 'Mocked someFunction!')
}));
// Import the function from the mocked module
const { someFunction } = require('./someFile');
test('snapshot', () => {
// Execute the mocked function
const someResult = someFunction();
// Expect to return the mocked value
expect(someResult).toBe('Mocked someFunction!');
});
});
You have to import the mocked modules after the jest.mock module mocking. You can create a jest.setup.js and configure it using setupFilesAfterEnv that can have your mocks inside it and then just import the modules like normal at the top of the test files.
I would like to test getFund() method from my service. I use NestJS that uses jest by default.
I have no idea how to test this line with jest: return await this.fundModel.findById(id);. Any idea?
import { Injectable } from '#nestjs/common';
import { Model } from 'mongoose';
import { Fund } from '../../funds/interfaces/fund.interface';
import { InjectModel } from '#nestjs/mongoose';
#Injectable()
export class FundService {
constructor(
#InjectModel('Fund')
private readonly fundModel: Model<Fund>,
) {}
/*****
SOME MORE CODE
****/
async getFund(id: string): Promise<Fund> {
return await this.fundModel.findById(id);
}
}
Edit
Thanks to slideshowp2 answer, I wrote this test.
describe('#getFund', () => {
it('should return a Promise of Fund', async () => {
let spy = jest.spyOn(service, 'getFund').mockImplementation(async () => {
return await Promise.resolve(FundMock as Fund);
});
service.getFund('');
expect(service.getFund).toHaveBeenCalled();
expect(await service.getFund('')).toEqual(FundMock);
spy.mockRestore();
});
});
The problem is that I get this result in my coverage report:
When I hover the line I get statement not covered.
There is only one statement return await this.fundModel.findById(id); in your getFund method. There is no other code logic which means the unit test you can do is only mock this.fundModel.findById(id) method and test
it .toBeCalledWith(someId).
We should mock each method and test the code logic in your getFund method. For now, there is no other code logic.
For example
async getFund(id: string): Promise<Fund> {
// we should mock this, because we should make an isolate environment for testing `getFund`
const fundModel = await this.fundModel.findById(id);
// Below branch we should test based on your mock value: fundModel
if(fundModel) {
return true
}
return false
}
Update
For example:
describe('#findById', () => {
it('should find ad subscription by id correctly', async () => {
(mockOpts.adSubscriptionDataSource.findById as jestMock).mockResolvedValueOnce({ adSubscriptionId: 1 });
const actualValue = await adSubscriptionService.findById(1);
expect(actualValue).toEqual({ adSubscriptionId: 1 });
expect(mockOpts.adSubscriptionDataSource.findById).toBeCalledWith(1);
});
});
The test coverage report: