The jest mocking only works on imported functions. In your apiMiddleware.js the default function is calling callApi variable, not the "exported" callApi function. To make the mock work, move callApi into its own module, and import it in apiMiddleware.js
Good question!
Answer from Herman Starikov on Stack OverflowThe jest mocking only works on imported functions. In your apiMiddleware.js the default function is calling callApi variable, not the "exported" callApi function. To make the mock work, move callApi into its own module, and import it in apiMiddleware.js
Good question!
I solved my issues converting my code to a Class, example:
// Implementation
export class Location {
getLocation() {
const environment = this.getEnvironmentVariable();
return environment === "1" ? "USA" : "GLOBAL";
}
getEnvironmentVariable() {
return process.env.REACT_APP_LOCATION;
}
}
// Test
import { Location } from "./config";
test('location', () => {
const config = new Location();
jest.spyOn(config, "getEnvironmentVariable").mockReturnValue("1")
const location = config.getLocation();
expect(location).toBe("USA");
});
Jest - when using spyOn function ensure the spied one is not called
jestjs - jest.spyOn not calling mocked implementation but rather actual function instead - Stack Overflow
Jest spyOn.mockImplementation calls actual method
reactjs - What is the difference between jest.fn() and jest.spyOn() methods in jest? - Stack Overflow
If you want to override the createNewGameWithInitialPlayer and return what you want then, you have to mock the import of GameFactory class in your test.
// Here you are creating your mock and saying the default return Game object
const mockCreateNewGameWithInitialPlayer = jest.fn().mockImplementation(() => new Game());
// Here you say to jest that any file who wants to import "GameFactory"
// will import this fake class
jest.mock('rootofGameFactory/GameFactory', () => ({
GameFactory: function(){
return {
// and when any file wants to execute this method, will execute my mock
createNewGameWithInitialPlayer: mockCreateNewGameWithInitialPlayer
}
}
}))
describe('Testing Game Service', () => {
test('createRoom', () => {
const response: RoomResponse = gameService.createRoom({
name: 'Player 1',
device: DevicesEnum.ios,
socketID: 'some-socket-id'
})
...
}
...
}
If you want to change the return object of your mocked method, you have to do it like this...
test('createRoom 2', () => {
//Here you say to jest, just ONCE (for this test) return an instance of Game2
mockCreateNewGameWithInitialPlayer.mockImplementationOnce(() => new Game2())
const response: RoomResponse = gameService.createRoom({
name: 'Player 1',
device: DevicesEnum.ios,
socketID: 'some-socket-id'
})
...
}
The jest documentation mentions
By default, jest.spyOn also calls the spied method. This is different behavior from most other test libraries. If you want to overwrite the original function, you can use jest.spyOn(object, methodName).mockImplementation(() => customImplementation) or jest.replaceProperty(object, methodName, jest.fn(() => customImplementation));
So you could do something like
jest.spyOn(gameFactory, 'GameFactory').mockImplementation(() => { return undefined })
From my experience, the issue is you're resetting the original mock's intent. When you create a spy, it has its own implementation, by overriding it with mockImplementation, I've experienced the scenario you are describing - instead, try this:
cmp.offsetPopoverPosition = jest.fn().mockImplementation(() => {
console.log('in the mock');
});
const mockListener = jest.spyOn(cmp, 'offsetPopoverPosition');
// ... do work
expect(mockListener).toHaveBeenCalledTimes,With
also this assumes that cmp is an instance of the component and not just it's definition reference
edit: please note that mocking out a messaged function inside of the component you are testing is a misguided approach to unit testing. Instead of testing communication to the sameComponent.method - test any messaging that chained method uses outside of the component being tested - With the brief question content, please ignore the testing approach advice I've given if its reading tea leaves and not relevant to your unit test design(s)
As suggested in the GitHub issue, the only thing that ended up working for me was to call the target function from the import of the same file. Something like this in React.
// cmp.js
import * as thisModule from './cmp';
offsetPopoverPosition = () => {}
ngAfterViewInit = () => {
thisModule.offsetPopoverPosition();
}
It's a weird issue...
The optimal solution does seem to just avoid this scenario by testing other items however (see this other answer).
My simple understanding of these two functions in react/frontend projects is the following:
jest.fn()
- You want to mock a function and really don't care about the original implementation of that function (it will be overridden by
jest.fn()) - Often you just mock the return value
- This is very helpful if you want to remove dependencies to the backend (e.g. when calling backend API) or third party libraries in your tests
- It is also extremely helpful if you want to make real unit tests. You don't care about if certain function that gets called by the unit you test is working properly, because thats not part of it's responsibility.
jest.spyOn()
- The original implementation of the function is relevant for your test, but:
- You want to add your own implementation just for a specific scenario and then reset it again via
mockRestore()(if you just use ajest.spyOn()without mocking it further it will still call the original function by default) - You just want to see if the function was called
- ...
- You want to add your own implementation just for a specific scenario and then reset it again via
- I think this is especially helpful for integration tests, but not only for them!
(Good blog post: https://medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c)
To my understanding the only difference is that YOU CAN RESTORE ORIGINAL FUNCTION with jest.spyOn and you can't with jest.fn.
Imagine we have some hook that calls a function when component is rendered, here we can just check the function was called, we do not test that function.
Another case if we want original function to test how it works. And we need both in one test file.
Real method:
myMethod() {
return 33;
}
With jest.fn()
const myMethod = jest.fn().mockImplementation(() => 25);
const result = myMethod();
expect(result).toBe(25);
In case we want to test now real myMethod, we can't restore it back to normal with jest.fn().
Another thing with spies:
const spy_myMethod = jest.spyOn(component, "myMethod").mockImplementation(() => 25);
const result = myMethod();
expect(result).toBe(25);
And now if we want the original myMethod
spy_myMethod.mockRestore();
const result = myMethod();
expect(result).toBe(33);
Why can't you do something similar to as follows?
const spy = jest.spyOn(instance, 'function');
spy
.mockImplementationOnce(() => originalInstanceFunction())
.mockImplementation((args) => args);
Here is an example implementation - Note had to store a reference to the original instance function
const original = {
func: (args) => { console.log(`original ${args}`)}
};
describe('test', () => {
it('should call original then mock', () => {
const originalFunction = original.func;
const spy = jest.spyOn(original, 'func');
spy.mockImplementationOnce((args) => originalFunction(args))
.mockImplementation((args) => console.log(`mock ${args}`));
original.func('test-args');
original.func('test-args');
expect(spy).toBeCalledTimes(2);
});
});
Outputs:
console.log
original test-args
at originalFunction (test.test.js:2:28)
console.log
mock test-args
at Object.spy.mockImplementationOnce.mockImplementation.args (test.test.js:12:42)
To do this with a mocked library, use jest.requireActual(). Copying an example from the guide:
jest.mock('node-fetch');
const fetch = jest.requireActual('node-fetch');
This allows you to mock the fetch library in your code being tested, but use the real fetch function in your test itself.
You were almost done without any changes besides how you spyOn.
When you use the spy, you have two options: spyOn the App.prototype, or component component.instance().
const spy = jest.spyOn(Class.prototype, "method")
The order of attaching the spy on the class prototype and rendering (shallow rendering) your instance is important.
const spy = jest.spyOn(App.prototype, "myClickFn");
const instance = shallow(<App />);
The App.prototype bit on the first line there are what you needed to make things work. A JavaScript class doesn't have any of its methods until you instantiate it with new MyClass(), or you dip into the MyClass.prototype. For your particular question, you just needed to spy on the App.prototype method myClickFn.
jest.spyOn(component.instance(), "method")
const component = shallow(<App />);
const spy = jest.spyOn(component.instance(), "myClickFn");
This method requires a shallow/render/mount instance of a React.Component to be available. Essentially spyOn is just looking for something to hijack and shove into a jest.fn(). It could be:
A plain object:
const obj = {a: x => (true)};
const spy = jest.spyOn(obj, "a");
A class:
class Foo {
bar() {}
}
const nope = jest.spyOn(Foo, "bar");
// THROWS ERROR. Foo has no "bar" method.
// Only an instance of Foo has "bar".
const fooSpy = jest.spyOn(Foo.prototype, "bar");
// Any call to "bar" will trigger this spy; prototype or instance
const fooInstance = new Foo();
const fooInstanceSpy = jest.spyOn(fooInstance, "bar");
// Any call fooInstance makes to "bar" will trigger this spy.
Or a React.Component instance:
const component = shallow(<App />);
/*
component.instance()
-> {myClickFn: f(), render: f(), ...etc}
*/
const spy = jest.spyOn(component.instance(), "myClickFn");
Or a React.Component.prototype:
/*
App.prototype
-> {myClickFn: f(), render: f(), ...etc}
*/
const spy = jest.spyOn(App.prototype, "myClickFn");
// Any call to "myClickFn" from any instance of App will trigger this spy.
I've used and seen both methods. When I have a beforeEach() or beforeAll() block, I might go with the first approach. If I just need a quick spy, I'll use the second. Just mind the order of attaching the spy.
EDIT:
If you want to check the side effects of your myClickFn you can just invoke it in a separate test.
const app = shallow(<App />);
app.instance().myClickFn()
/*
Now assert your function does what it is supposed to do...
eg.
expect(app.state("foo")).toEqual("bar");
*/
EDIT:
Here is an example of using a functional component. Keep in mind that any methods scoped within your functional component are not available for spying. You would be spying on function props passed into your functional component and testing the invocation of those. This example explores the use of jest.fn() as opposed to jest.spyOn, both of which share the mock function API. While it does not answer the original question, it still provides insight on other techniques that could suit cases indirectly related to the question.
function Component({ myClickFn, items }) {
const handleClick = (id) => {
return () => myClickFn(id);
};
return (<>
{items.map(({id, name}) => (
<div key={id} onClick={handleClick(id)}>{name}</div>
))}
</>);
}
const props = { myClickFn: jest.fn(), items: [/*...{id, name}*/] };
const component = render(<Component {...props} />);
// Do stuff to fire a click event
expect(props.myClickFn).toHaveBeenCalledWith(/*whatever*/);
If a functional component is niladic (no props or arguments) then you can use Jest to spy on any effects you expect from the click method:
import { myAction } from 'src/myActions'
function MyComponent() {
const dispatch = useDispatch()
const handleClick = (e) => dispatch(myAction('foobar'))
return <button onClick={handleClick}>do it</button>
}
// Testing:
const { myAction } = require('src/myActions') // Grab effect actions or whatever file handles the effects.
jest.mock('src/myActions') // Mock the import
// Do the click
expect(myAction).toHaveBeenCalledWith('foobar')
You're almost there. Although I agree with @Alex Young answer about using props for that, you simply need a reference to the instance before trying to spy on the method.
describe('my sweet test', () => {
it('clicks it', () => {
const app = shallow(<App />)
const instance = app.instance()
const spy = jest.spyOn(instance, 'myClickFunc')
instance.forceUpdate();
const p = app.find('.App-intro')
p.simulate('click')
expect(spy).toHaveBeenCalled()
})
})
Docs: http://airbnb.io/enzyme/docs/api/ShallowWrapper/instance.html