here is the basic setup
component.js
import {functionIWantToMock, val} from "somewhere/on/my/hdd"
export function Component() {
return (<>
<div>{val}</div>
<div>{functionIWantToMock()}</div>
</>)
}
component.test.js
import { Component } from "./component";
import { render, screen } from "#testing-library/react";
it("should change the result of the function", () => {
// Change behaviour of functionIWantToMock
functionIWantToMock = () => {
return "empty"
}
render(<Component/>);
// This is not a proper assertion
expect(screen.getByRole("img")).toBeVisible()
})
I've looked at manual mocking, spying and jest.fn() but none of those will work, i think, because the call is happening within the component and so I can't mock it globally.
What is the correct way to mock functionIWantToMock so that i can change its behaviour in the component for the test?
You can either use module mocking or dependency injection for this.
Module mocking
The Jest docs have an example of mocking a module, you would need to do this:
component.test.js
import { Component } from "./component";
import { render, screen } from "#testing-library/react";
// This gives you a handle to the function so you can mock
// the return value, make expectations on it, etc
import { functionIWantToMock } from "somewhere/on/my/hdd";
// This mocks all exports from the module
jest.mock("somewhere/on/my/hdd");
it("should change the result of the function", () => {
// Change behaviour of functionIWantToMock
functionIWantToMock.mockReturnValue("empty");
render(<Component/>);
expect(functionIWantToMock).toBeCalled()
})
I recommend adding clearMocks: true to your Jest config to avoid leaking state between tests.
Dependency Injection
Your other option is to pass the function into the component:
component.js
import {functionIWantToMock as defaultFn, val} from "somewhere/on/my/hdd"
// Using the import as the default function helps you avoid
// passing the function where this component is used.
export function Component({functionIWantToMock = defaultFn}) {
return (<>
<div>{val}</div>
<div>{functionIWantToMock()}</div>
</>)
}
component.test.js
import { Component } from "./component";
import { render, screen } from "#testing-library/react";
it("should change the result of the function", () => {
const mockFn = jest.fn(() => {
return "empty"
})
render(<Component functionIWantToMock={mockFn} />);
expect(mockFn).toBeCalled()
})
Related
Scroll.js
import React from "react";
export const ScrollToTop = ({ children, location }) => {
React.useEffect(() => window.scrollTo(0, 0), [location.pathname]);
return children;
};
Scroll.test.js
import React from "react";
import { ScrollToTop } from "./ScrollToTop";
describe("ScrollToTop", () => {
it("", () => {
expect(
ScrollToTop({
children: "some children",
location: { pathname: "the path" }
})
).toEqual();
});
});
and the result I'm getting is
enter image description here
You should not call ScrollToTop as a function directly, this is what error message is complaining about.
React docs recommend the Testing Library for writing tests.
Here is an example of how you can write Scroll.test.js using the library above:
import React from "react";
import { render } from '#testing-library/react';
import { ScrollToTop } from "./ScrollToTop";
describe("ScrollToTop", () => {
it('calls window.scrollTo()', () => {
window.scrollTo = jest.fn(); // create a moack function and record all calls
render(<ScrollToTop location={{ pathname: 'pathname' }}>Text</ScrollToTop>); // render a component
expect(window.scrollTo).toHaveBeenCalledWith(0, 0); // check that scrollTo mock was called
});
});
I want to write a test to see if a useEffect function inside my component is called if the correct prop is passed in.
PropDetail.js
import React, { useState, useEffect } from "react";
function PropDetail({ propID }) {
const [propStatus, setPropStatus] = useState(null);
useEffect(() => {
if (!propID) return;
setPropStatus(propID)
}, [propID]);
return <p>{propStatus}</p>
}
export default PropDetail;
PropDetail.test.js
import React from "react";
import { shallow } from "enzyme";
import PropDetail from '../PropDetail'
const props = { }
describe('PropDetail', () => {
const wrapper = shallow(<PropDetail {...props} />)
describe('With no propID', () => {
it('returns null if no propID passed in', () => {
expect(wrapper.html()).toBe(null)
})
})
describe('With propID passed in', () => {
beforeEach(() => {
wrapper.setProps({ propID: 'PROPID' })
})
it('Runs useEffect with propID', () => {
expect(wrapper.setPropStatus().toHaveBeenCalledTimes(1);
})
})
})
The errors i'm getting in the console are 'Matcher error: received value must be a mock or spy function' and 'Received has value: undefined'.
I'm quite new to writing tests so i'm not sure if this is the right starting point or if i'm testing the wrong thing!
You don't want to test a feature of React such as useEffect. You'll want to test your code in a way such as 'what happens when useEffect has been called'.
You could test the value of propStatus upon initial rendering. Then make an update to the propID, then test the value of propStatus. Is the the value you expect or desire?
This way you're testing the actual code you wrote for correctness. When you see a change in propStatus, you'll also know that useEffect was called without directly testing that function.
You can also test for coverage by using yarn test --coverage.
I have a simple connected component that, when loaded, builds the user details by calling an async action and then displays those details from state.
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { loadUserDetails } from '../actions/user';
class ShowUserDetailsLite extends React.Component {
componentDidMount() {
this.props.loadUserDetails();
}
render() {
const { userDetails } = this.props;
const handleClickEdit = () => {
history.push(editProfilePath());
};
return (
<div>
<div className="email">
{userDetails.email_address}
</div>
<div className="name">
{userDetails.first_name} {userDetails.last_name}
</div>
</div>
);
}
}
ShowUserDetailsLite.propTypes = {
loadUserDetails: PropTypes.func.isRequired,
userDetails: PropTypes.shape({}).isRequired,
};
const mapDispatchToProps = {
loadUserDetails,
};
function mapStateToProps(state) {
const userDetails = state.user.details;
return { userDetails };
}
export default connect(mapStateToProps, mapDispatchToProps)(ShowUserDetailsLite);
To start, I'd like to test that my component is displaying the correct information from state, so I have the following test.
import configureStore from 'redux-mock-store'
import { shallow } from 'enzyme';
import { expect } from 'chai';
import React from 'react'
import Provider from 'react-redux';
import ShowUserDetailsLite from '../../components/ShowUserDetailsLite'
describe('ShowUserDetailsLite component', () => {
it('displays the current user', () => {
const mockStore = configureStore();
let store = mockStore({
user: {
details: {
email_address: 'test#test.com',
first_name: 'First',
last_name: 'Last',
}
},
});
let wrapper = shallow(<ShowUserDetailsLite store={store} loadUserDetails={jest.fn()}/>).dive()
expect(wrapper.find('.email').text()).to.eql('test#test.com')
})
})
When I run this test with the componentDidMount function commented out it works great, the state is read and the correct information is displayed, but when I run the test including the componentDidMount function the test attempts to call the function and I get the following error:
FAIL app/tests/components/ShowUserDetailsLite.test.js
ShowUserDetailsLite component
✕ displays the current user (32ms)
● ShowUserDetailsLite component › displays the current user
ReferenceError: regeneratorRuntime is not defined
16 |
17 | export function loadUserDetails() {
> 18 | return async dispatch => {
| ^
19 | try {
20 | const res = await axios.get(`/${window.realm}/api/userDetails`);
21 | dispatch({ type: SET_USER_DETAILS, data: res.data });
At this stage I don't care about testing the loadUserDetails function so I simply want to stub it. I understood that to do that you simply need to pass the function in as a property, which I've attempted to do by passing in a jest function:
let wrapper = shallow(<ShowUserDetailsLite store={store} loadUserDetails={jest.fn()}/>).dive()
But still I'm getting the error. How do I properly stub async actions called in componentdidmount for connected component tests?
I come probably to late but i ended up here struggeling too with the same problem ,after digging for 2 days i founded that :
Since you wrap your component with Connect you need to pass your stubbed props function to the child component
Your can export your component without the HOC connect
export ShowUserDetailsLite
then import it
import {ShowUserDetailsLite} from ...
And now you have a shallowed component without connect you can directly pass the props like the users and the loadUserDetails mocked
To be more complete with the original question :
How to stub componentDidMount
When you mount or shallow your component you will fire componentDidMount .
With shallow
for shallow rendering your can use the option {disableLifecycleMethods:true}
wrapper = shallow(
<Stats {...props} />, {disableLifecycleMethods:true}
)
With mount
If you want to use mount for rendering you need to stub componentDidMount doing this way :
jest.spyOn(YOUCOMPONENT.prototype, 'componentDidMount').mockImplementation();
then you mount your component
wrapper = mount(
<Stats {...props} />
)
And now you can have access to the methods if you want to stub a method called in the componentDidMount:
const spy = jest.spyOn(wrapper.instance(), 'myMethod').mockImplementation(() => {//do stuff})
I have a Login component as below and I am writing some test cases for this component. When I tried to run the test I got the following error:
Test
import renderer from 'react-test-renderer'
import Login from '../Login'
let props, wrapper
beforeEach(() => {
props = {
loginAttempt: jest.fn(),
recoverAttempt: jest.fn(),
reset: jest.fn()
}
wrapper = shallow(<Login {...props} />)
})
describe('tests for <Login />', () => {
test('should have a formProvider with handlesubmit atribute', () => {
const value = wrapper.find('FormProvider')
expect(value.length).toBe(1)
})
})
//Snapshot test
test('Snapshot test for the Contact form', () => {
const tree = renderer.create(<Login {...props} />).toJSON()
expect(tree).toMatchSnapshot()
})
Component
import React, { Component } from 'react'
import KeyboardAvoidingWrapper from 'components/Wrappers/KeyboardAvoidingWrapper'
export default class AuthScreen extends Component {
state = {
}
toggleRecovery = e => {
)
}
loginAttempt = data => {
}
recoverAttempt = data => {
}
componentWillUnmount() {
}
render() {
let { loginAttempt, toggleRecovery, recoverAttempt, state, props } = this
let { recovery } = state
let { error, fetching } = props
return (
<KeyboardAvoidingWrapper enabled={false} behavior="padding" fluid>
UI GOES HERE..
</KeyboardAvoidingWrapper>
)
}
}
Error
● Test suite failed to run
Invariant Violation: Native module cannot be null.
at invariant (node_modules/react-native/node_modules/fbjs/lib/invariant.js:40:15)
at new NativeEventEmitter (node_modules/react-native/Libraries/EventEmitter/NativeEventEmitter.js:36:36)
at Object.<anonymous> (node_modules/react-native-safari-view/SafariViewManager.ios.js:12:20)
at Object.<anonymous> (node_modules/react-native-safari-view/index.js:1:238)
Why I am getting this error? Is it because the component does not get imported correctly? I could not figure out the why this happening. How can I solve this issue?
This problem happens when you import a native component in the render tree, as the test renderer do not have them. To fix this, either you need to mock the component (https://jestjs.io/docs/en/manual-mocks), or use shallow rendering (https://reactjs.org/docs/shallow-renderer.html)
For your particular case, this is the github issue to help you: https://github.com/naoufal/react-native-safari-view/issues/99
Another solution could be using react-native-mock-render module (the most active fork of react-native-mock)
I have a subscriber that dispatches an action based on the parameters supplied to a pub event
// subscriptions.js
import store from '../store';
import { action } from '../actions';
export const subscribeToToggle = () => {
window.$.subscribe('action/toggle', (_e, isToggleOn) => {
if (isToggleOn=== true){
store.dispatch(action());
}
});
};
In my test file I write 2 tests that test that the action is sent only when true is supplied.
// subscriptions.test.js
import { subscribeToToggle } from './subscriptions';
import jQuery from 'jquery';
import { actionTypes } from '../constants';
import store from '../store';
jest.mock('../store');
beforeEach(() => {
window.$ = jQuery;
window.$.unsubscribe('action/toggle');
});
test('Action not sent when false', () => {
subscribeToToggleOpen();
window.$.publish('action/toggle', false);
expect(store.getActions().length).toBe(0);
});
test('Action sent when true', () => {
subscribeToToggleOpen();
window.$.publish('action/toggle', true);
expect(store.getActions().length).toBe(1);
expect(store.getActions()[0].type).toBe(actionTypes.ACTION);
});
I have the following mocked store using redux-test-utils
import { createMockStore } from 'redux-test-utils';
let store = null;
store = createMockStore('');
export default store;
The issue I face is that my test only pass when the false test comes first. If they are the other way around the 'Action not sent when false' test fails as it sees the action supplied by the 'Action sent when true' test.
Is there any way for me to use the beforeEach method to reset the mocked store object?
In this case, the problem is that your store is essentially a singleton. This can create issues when you are trying to do things like this and is generally kind of an anti-pattern.
Instead of exporting a store object, it'd probably be better if you exported a getStore() function which could be called to get the store. In that case, you could then do:
getStore().dispatch(action());
Inside of that, you could then have other helper functions to be able to replace the store that is being returned by it. That file could look something like this:
import { createMockStore } from 'redux-test-utils';
let store = createMockStore('');
export default () => store;
Then, inside of there, you can add another which could be resetStore as a non-default export:
export const resetStore = () => store = createMockStore('');
It would still technically be a singleton, but it's now a singleton you can have some control over.
Then, in your beforeEach() in your tests, just call resetStore():
import { resetStore } from '../store';
beforeEach(() => {
resetStore();
});
This would also require you to update your real code to use getStore() instead of store directly, but it'll probably be a beneficial change in the long run.
Complete Updated Version:
// subscriptions.js
import getStore from '../store';
import { action } from '../actions';
export const subscribeToToggle = () => {
window.$.subscribe('action/toggle', (_e, isToggleOn) => {
if (isToggleOn=== true){
getStore().dispatch(action());
}
});
};
// subscriptions.test.js
import { subscribeToToggle } from './subscriptions';
import jQuery from 'jquery';
import { actionTypes } from '../constants';
import getStore, { resetStore } from '../store';
jest.mock('../store');
beforeEach(() => {
window.$ = jQuery;
window.$.unsubscribe('action/toggle');
resetStore();
});
test('Action not sent when false', () => {
subscribeToToggleOpen();
window.$.publish('action/toggle', false);
expect(getStore().getActions().length).toBe(0);
});
test('Action sent when true', () => {
subscribeToToggleOpen();
window.$.publish('action/toggle', true);
expect(getStore().getActions().length).toBe(1);
expect(getStore().getActions()[0].type).toBe(actionTypes.ACTION);
});
import { createMockStore } from 'redux-test-utils';
let store;
export const resetStore = () => { store = createMockStore(''); }
resetStore(); // init the store, call function to keep DRY
export default () => store;
Beyond that, the other way would be to have a global reducer which could reset the state of the store to it's default, but that would be messier and I don't really think would generally fit with writing a unit test.