How to mock a component method? - javascript

I'm simply trying to find out if a component method has been called after a store action, but I'm getting this error:
expect(jest.fn())[.not].toHaveBeenCalled()
jest.fn() value must be a mock function or spy.
Received:
function: [Function bound mockConstructor]
This is my unit test:
describe('MyComponent.spec.js', () => {
let methods = {
setLocation: jest.fn()
// more methods...
}
it('calls setLocation on undo/redo', () => {
let wrapper = mount(MyComponent, {
store,
localVue,
methods
})
store.dispatch('doUndo')
expect(wrapper.vm.setLocation).toHaveBeenCalled()
})
})
Not sure if this is a good practice or not, but I'm using the actual store and a local Vue instance.

To verify the mocked method, use the actual mock variable itself (not via wrapper):
expect(methods.setLocation).toHaveBeenCalled()

Related

How to mock global Object with Jest in React Native

I have the function:
const someFn = () => {
const keys = Object.keys(someObj);
// rest of function
}
In my unit test (with jest), I want to mock the return value of Object.keys:
test('some test', () => {
jest.mocked(Object.keys).mockReturnValue(['one', 'two']);
// rest of test
});
But that resulting the following error:
TypeError: jest.mocked(...).mockReturnValue is not a function
Usually It means that I need to first mock the export itself using jest.mock, but with global Object I'm not sure how to do it. I thought maybe:
global.Object = {
keys: jest.fn(),
}
But that doesn't seem to work at all.
Any idea how to solve this issue? thanks :)

How to test a mocked method that is used as a called function parameter -in a curry function- and not as a direct called function?

Here is the code to be tested, a small logger module, built as a composed object, this object has keys that are represented by curry functions. These curry functions have as first parameter a method that actually implements the method to be called (that requires a test).
Basically I would like to check that console.log or console.info are called.
logger.ts
const log = (logFn: () => void) => (message: any) => {
logFn(message)
}
const logger = {
debug: log(console.log),
info: log(console.info)
}
export default logger
logger.spec.ts
import logger from './logger'
describe('logger', () => {
describe('debug', () => {
it('sets a text as log output', () => {
const consoleSpy = jest.spyOn(console, 'log')
const text = 'Export is working properly'
logger.debug(text)
expect(consoleSpy).toHaveBeenCalledTimes(1) // failure
expect(consoleSpy).toHaveBeenCalledWith(text) // failure
console.log(text)
expect(consoleSpy).toHaveBeenCalledTimes(1) // success
expect(consoleSpy).toHaveBeenCalledWith(text) // success
})
})
})
But what happens is that, if I call console.log directly in my logger.ts, the test detects that the method is invoked, while, if I call my module method logFn that invokes indirectly console.log through a curry function, then the test does not detect it. I guess that the level of mocking is not reaching the proper scope. Anyway question is, is there a way to manage it? Thanks
it looks like the mocks are working correctly, because if you wrap the call console.log in an arrow function like debug: log((message: any) => console.log(message)), the test passes.

Angular - How can I mock an external function in a unit test?

I'm trying to write an angular unit test for a function that has a dependency on an imported function - how can write a unit test that mocks the results of the imported function? The function I have is:
import { DependentMethod } from './dependentMethod';
export function doSomething() {
const dependentResults = DependentMethod(); // DependentMethod makes an http call and returns the result
return dependentResults;
}
In this case, I want to test the doSomething and mock the DependentMethod function. When I've tried to mock stuff before, I've used spy on class methods but I'm not sure how to handle it in this case. Any help would be greatly appreciated!
You can try something as below,
import { dependent } from './dependentLibrary';
describe('yourCoponent', () => {
let component: yourComponent;
let service : dependentService;
beforeEach(() => {
service = new dependent(null); // if your service has further dependency
component = new yourComponent(service);
});
it('should perform test', () => {
// Arrange part -->
spyOn(service, 'dependentMethod').and.callFake(() => {
return Observable.from([ [1,2,3] ]); // return your mock data
});
//Act part
component.ngOnInit();
//Assert
expect(component.testable.action).toBe(value);
});

Jest not mocking module when using "doMock" to avoid hoisting

I'm trying to mock a class that creates and returns another class. I've got this:
const mockMethod = jest.fn();
const mockClassA = jest.fn<ClassA>(() => ({
method: mockMethod
}));
jest.mock("../src/ClassB", () => ({
ClassB: {
getClassA: () => new mockClassA()
}
}));
It gets caught out because of the hoisting, the mockClassA is undefined when jest is mocking `../src/ClassB".
I've read if you don't want hoisting, just use doMock instead:
When using babel-jest, calls to mock will automatically be hoisted to the top of the code block. Use this method if you want to explicitly avoid this behavior.
https://jestjs.io/docs/en/next/jest-object#jestdomockmodulename-factory-options
When I run with mock, I get TypeError: mockClassA is not a constructor, as mockClassA is undefined because the mock is hoisted above the definition for mockClassA.
When I change mock to doMock, it simply does not mock the module - it uses the real thing.
Edit: Declairing them in-line means I can't easily access the mocked methods for checking:
jest.mock("../src/ClassB", () => ({
ClassB: {
getClassA: () => ({
method: jest.fn()
})
}
}));
Because getClassA is a function, it's returning a separate instance of the object with method.
Edit 2: Ah! Managed to inline it like so:
jest.mock("../src/ClassB", () => {
const mockMethod: jest.fn();
return {
ClassB: {
getClassA: () => ({
method: mockMethod
})
}
};
});
I think you have 2 options here:
use jest.mock, inline the mockClassA and mockMethod, expose them in the mock and then import from '../src/ClassB'
use doMock, but use a dynamic require within your test case.

Cannot mock a module with jest, and test function calls

I create a project using create-app-component, which configures a new app with build scripts (babel, webpack, jest).
I wrote a React component that I'm trying to test. The component is requiring another javascript file, exposing a function.
My search.js file
export {
search,
}
function search(){
// does things
return Promise.resolve('foo')
}
My react component:
import React from 'react'
import { search } from './search.js'
import SearchResults from './SearchResults'
export default SearchContainer {
constructor(){
this.state = {
query: "hello world"
}
}
componentDidMount(){
search(this.state.query)
.then(result => { this.setState({ result, })})
}
render() {
return <SearchResults
result={this.state.result}
/>
}
}
In my unit tests, I want to check that the method search was called with the correct arguments.
My tests look something like that:
import React from 'react';
import { shallow } from 'enzyme';
import should from 'should/as-function';
import SearchResults from './SearchResults';
let mockPromise;
jest.mock('./search.js', () => {
return { search: jest.fn(() => mockPromise)};
});
import SearchContainer from './SearchContainer';
describe('<SearchContainer />', () => {
it('should call the search module', () => {
const result = { foo: 'bar' }
mockPromise = Promise.resolve(result);
const wrapper = shallow(<SearchContainer />);
wrapper.instance().componentDidMount();
mockPromise.then(() => {
const searchResults = wrapper.find(SearchResults).first();
should(searchResults.prop('result')).equal(result);
})
})
});
I already had a hard time to figure out how to make jest.mock work, because it requires variables to be prefixed by mock.
But if I want to test arguments to the method search, I need to make the mocked function available in my tests.
If I transform the mocking part, to use a variable:
const mockSearch = jest.fn(() => mockPromise)
jest.mock('./search.js', () => {
return { search: mockSearch};
});
I get this error:
TypeError: (0 , _search.search) is not a function
Whatever I try to have access to the jest.fn and test the arguments, I cannot make it work.
What am I doing wrong?
The problem
The reason you're getting that error has to do with how various operations are hoisted.
Even though in your original code you only import SearchContainer after assigning a value to mockSearch and calling jest's mock, the specs point out that: Before instantiating a module, all of the modules it requested must be available.
Therefore, at the time SearchContainer is imported, and in turn imports search , your mockSearch variable is still undefined.
One might find this strange, as it would also seem to imply search.js isn't mocked yet, and so mocking wouldn't work at all. Fortunately, (babel-)jest makes sure to hoist calls to mock and similar functions even higher than the imports, so that mocking will work.
Nevertheless, the assignment of mockSearch, which is referenced by the mock's function, will not be hoisted with the mock call. So, the order of relevant operations will be something like:
Set a mock factory for ./search.js
Import all dependencies, which will call the mock factory for a function to give the component
Assign a value to mockSearch
When step 2 happens, the search function passed to the component will be undefined, and the assignment at step 3 is too late to change that.
Solution
If you create the mock function as part of the mock call (such that it'll be hoisted too), it'll have a valid value when it's imported by the component module, as your early example shows.
As you pointed out, the problem begins when you want to make the mocked function available in your tests. There is one obvious solution to this: separately import the module you've already mocked.
Since you now know jest mocking actually happens before imports, a trivial approach would be:
import { search } from './search.js'; // This will actually be the mock
jest.mock('./search.js', () => {
return { search: jest.fn(() => mockPromise) };
});
[...]
beforeEach(() => {
search.mockClear();
});
it('should call the search module', () => {
[...]
expect(search.mock.calls.length).toBe(1);
expect(search.mock.calls[0]).toEqual(expectedArgs);
});
In fact, you might want to replace:
import { search } from './search.js';
With:
const { search } = require.requireMock('./search.js');
This shouldn't make any functional difference, but might make what you're doing a bit more explicit (and should help anyone using a type-checking system such as Flow, so it doesn't think you're trying to call mock functions on the original search).
Additional note
All of this is only strictly necessary if what you need to mock is the default export of a module itself. Otherwise (as #publicJorn points out), you can simply re-assign the specific relevant member in the tests, like so:
import * as search from './search.js';
beforeEach(() => {
search.search = jest.fn(() => mockPromise);
});
In my case, I got this error because I failed to implement the mock correctly.
My failing code:
jest.mock('react-native-some-module', mockedModule);
When it should have been an arrow function...
jest.mock('react-native-some-module', () => mockedModule);
When mocking an api call with a response remember to async() => your test and await the wrapper update. My page did the typical componentDidMount => make API call => positive response set some state...however the state in the wrapper did not get updated...async and await fix that...this example is for brevity...
...otherImports etc...
const SomeApi = require.requireMock('../../api/someApi.js');
jest.mock('../../api/someApi.js', () => {
return {
GetSomeData: jest.fn()
};
});
beforeEach(() => {
// Clear any calls to the mocks.
SomeApi.GetSomeData.mockClear();
});
it("renders valid token", async () => {
const responseValue = {data:{
tokenIsValid:true}};
SomeApi.GetSomeData.mockResolvedValue(responseValue);
let wrapper = shallow(<MyPage {...props} />);
expect(wrapper).not.toBeNull();
await wrapper.update();
const state = wrapper.instance().state;
expect(state.tokenIsValid).toBe(true);
});

Categories