React.js and Jasmine Spies - unit-testing

Using basic test-utils and jasmine for unit testing.
How do you spy on a function inside a react component?
test.js:
class Test extends React.Component {
handleClick() {
// Do something
}
render() {
return (
<div className="test-class" onClick={this.handleClick}>Test</div>
);
}
}
const React = require('react-with-addons');
const RequireJsTest = require('requirejs-test');
const Utils = React.addons.TestUtils;
const Test = require('./test');
describe('test', () => {
it('should test', function() {
const test = Utils.renderIntoDocument(<Test/>);
const el = Utils.findRenderedDOMComponentWithClass(test, 'test-class');
spyOn(test, 'handleClick');
Utils.Simulate.click(el);
expect(test.handleClick).toHaveBeenCalled();
});
});
I'm getting the following error:
Expected spy handleClick to have been called. (1)
Any ideas? Thanks!

To be honest, I haven't tested react apps yet, but try the method (the last test in describe block), which I've just found in enzyme readme doc.
I think you should spy on component class prototype method before rendering component:
spyOn(Test.prototype, 'handleClick');
// and then
expect(Test.prototype.handleClick).toHaveBeenCalled();

Related

How to stub Vue component methods for unit testing

How can I stub certain methods (getters, in particular) from Vue single file components for unit testing with mocha/expect?
The problem I was facing was the following:
I have a component with a get method someData
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import SomeService from '#/services/some.service'
#Component()
export default class MyApp extends Vue {
...
mounted () {
...
}
get someData () {
return this.$route.path.split('/')[1] || 'main'
}
get getLocation () {
return this.someService.getBaseURL()
}
initSomeStringProperty (): string {
return 'some string'
}
}
</script>
My tests always fail with:
[Vue warn]: Error in render: "TypeError: Cannot read property 'path' of undefined"
When I try to stub the method using sinon, like following:
describe('MyApp.vue', () => {
if('returns main', () => {
const dataStub = sinon.stub(MyApp, 'someData')
listStub.yields(undefined, 'main')
const wrapper = shallowMount(AppNavBar)
expect(wrapper.text()).to.include('Some Content')
})
})
However, I get the following error:
TypeError: Cannot stub non-existent own property someData
In addition, I get the same error for every other method, I want to stub analogously, e.g., initSomeStringProperty().
In the code above someData is computed property that is defined with property accessor through vue-property-decorator.
It can be stubbed at two points, either on class prototype:
sinon.stub(MyApp.prototype, 'someData').get(() => 'foo');
Or component options:
sinon.stub(MyApp.options.computed.someData, 'get').value('foo');
You could set the component's computed props and methods upon mounting, as shown below. Update: As of 1.x, setting methods has been deprecated in favor of stubbing (see #EstusFlask's answer on how to properly stub with Sinon).
const wrapper = shallowMount(MyApp, {
computed: {
someData: () => 'foo'
},
methods: {
initSomeStringProperty: () => 'bar'
}
})
expect(wrapper.vm.someData).to.equal('foo')
expect(wrapper.vm.initSomeStringProperty()).to.equal('bar')
If you were just trying to avoid the error about $route being undefined, you could mock $route upon mounting:
const wrapper = shallowMount(MyApp, {
mocks: {
$route: { path: '/home' }
}
})
expect(wrapper.vm.someData).to.equal('home')

Testing redux connected component

I have the following connected component in React-Redux
export class IncrementalSearch extends React.Component {
constructor(props) {
super(props);
this.onSearch$ = new Subject();
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
this.subscription = this.onSearch$
.debounceTime(300)
.subscribe(debounced => {
this.props.onPerformIncrementalSearch(debounced);
});
}
componentWillUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
onChange(e) {
const newText = e.target.value;
this.onSearch$.next(newText);
}
render() {
return (
<div className={styles.srchBoxContaner}>
<input
className={styles.incSrchTextBox}
type="text" name="search" id="searchInput" placeholder="Search.."
onChange={this.onChange}
/>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => ({
onPerformIncrementalSearch: (searchText) => {
dispatch(performIncrementalStoreSearch(searchText));
}
});
const IncrementalSearchComponent = connect(null, mapDispatchToProps)(IncrementalSearch);
export default IncrementalSearchComponent;
I'm now trying to write a unit tests for the connected component. I'm using Jest, Enzyme, and Sinon. So far this is what my unit test looks like
it('calls \'onPerformIncrementalSearch\' when the user types in something', () => {
const mockStore = configureStore();
const onPerformIncrementalSearchSpy = sinon.spy();
const mapStateToProps = null;
const mapDispatchToProps = {
onPerformIncrementalSearch: onPerformIncrementalSearchSpy
};
const mappedProps = { mapStateToProps, mapDispatchToProps };
const incrementalSearchWrapper =
mount(
<Provider store={mockStore}>
<IncrementalSearchComponent
onPerformIncrementalSearch={onPerformIncrementalSearchSpy}
props={mappedProps}
store={mockStore}
/>
</Provider>
);
//find the input element
const searchInput = incrementalSearchWrapper.find('#searchInput');
searchInput.node.value = 'David';
searchInput.simulate('change', searchInput);
expect(onPerformIncrementalSearchSpy.called).toEqual(true);
// onChangeSpy.restore();
});
However, when I run this test, I get the following error
TypeError: Cannot read property 'bind' of undefined
How do I fix this?
Testing connected components can be a huge pain. I find that it's more trouble than it's worth to try to wrap your components with a Provider to give them access to the store.
Instead, I would just export the component, mapStateToProps, and mapDispatchToProps and test them individually. Your app will still work the same if you export the connected component as the default.
Dan Abramov (Co author of Redux) suggests this approach in this comment
I would also suggest looking into enzyme shallow rendering instead of using mount when testing connected components.

How to test react component correctly?

Recently I am learning to test React with jest and enzyme, It seems hard to understand what a unit test is it, my code
import React from "react";
class App extends React.Component {
constructor() {
super();
this.state = {
value: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const value = e.target.value;
this.setState({
value
});
}
render() {
return <Nest value={this.state.value} handleChange={this.handleChange} />;
}
}
export const Nest = props => {
return <input value={props.value} onChange={props.handleChange} />;
};
export default App;
and my test
import React from "react";
import App, { Nest } from "./nest";
import { shallow, mount } from "enzyme";
it("should be goood", () => {
const handleChange = jest.fn();
const wrapper = mount(<App />);
wrapper.find("input").simulate("change", { target: { value: "test" } });
expect(handleChange).toHaveBeenCalledTimes(1);
});
IMO, the mocked handleClick will intercept the handleClick on App,
if this is totally wrong, what's the right way to use mock fn and test the handleClick be called.
Another: I search a lot, read the similar situations, seem like this iscontra-Unit Test,
Probably I should test the two component separately, I can test both components,
test the
<Nest value={value} handleChange={handleChange} />
by pass the props manually, and then handleChangeinvoked by simulate change
it passed test.
but how can I test the connection between the two?
I read
some work is React Team's Work
...
I don't know which parts I have to test in this case, and Which parts react already tested and don't need me to test. That's confusing.
You should take the path of testing the Nest component in isolation first, passing your mocked handleChange as a prop, to verify that input changes are being propagated.
If you want to test the state part, then you can get the instance of your App class from enzyme and call that method directly:
it("should update the Nest value prop when change is received", () => {
const wrapper = mount(<App />);
const instance = wrapper.instance()
instance.handleChange( { target: { value: "test" } })
const nestComponent = wrapper.find("Nest").first()
expect(nestComponent).prop('value').toEqual('test');
});
This a very very basic, almost not needed to test piece of code, but it will get your test coverage up if that's what you're after.
Doc for instance: http://airbnb.io/enzyme/docs/api/ReactWrapper/instance.html
If you want to test for the connection. From what I see, the nest component is a child component inside the App component. You could test that <App /> contains `.
describe('<App />', () => {
it('should contain a nest component', () => {
const wrapper = mount(<App />);
expect(wrapper.find(<Nest />)).toHaveLength(1);
});
});
Secondly, since the onChange event on the nest component updates the state in the App component, you can also test for state changes since its a behavior you expect.
it('should update state', () => {
//find input and simulate change with say {value: 'new value'} and then
expect(wrapper.state().value).toBe('newValue');
});
I hope this helps.

How to unit test a Redux action in a component inside a connected Redux component with Jest

I'm using jest and enzyme to unit test my React application and I'm struggling with testing connected components.
I do have a simple component which the following logic:
class LoginPage extends React.Component {
componentDidMount() {
if (!this.props.reduxReducer.appBootstrapped) {
this.props.dispatch(ReduxActions.fadeOutAndRemoveSplashScreen(500));
}
}
render() {
return (
<div data-page="login-page" >
<div>This is the login page.</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
reduxReducer: state.reduxReducer
}
};
export default connect(mapStateToProps, null)(LoginPage);
So, this is a component which displays a <div /> element containing some text, but the important part that I want to test is that when the component is mounted, an action is dispatched to hide the splash screen.
I want this only to happen when the application is not bootstrapped.
I do have a simple unit test to test that the component is rendered:
describe("[LoginPage Component]", () => {
it("Renders without a problem.", () => {
// Act.
const wrapper = mount(
<LoginPage store={ reduxStore } />
);
// Assert.
expect(wrapper.find("div[data-page=\"login-page\"]").length).toBe(1);
});
});
The reduxStore property is my actual redux store, created with the following code:
const reduxStore = createStore(
combineReducers(
{
reduxReducer
}
)
);
Now, how can I test the componentDidMount() method, and more in special, test that the redux action fadeOutAndRemoveSplashScreen() is only called when the application is not bootstrapped yet.
I do think that I need to mock my redux store, however, I'm a newbie on this and don't now how to get started, so an example will be highly appreciated.
If any other thoughts on my implementation, feel free to provide some advice.
Kind regards
I wouldn't use the raw dispatch method to send off an action. I would use mapDispatchToProps. This makes your action directly available in your component props - here we use ES6 destructing as a short hand in the connect method.
Then instead of mocking the redux store I would just test your component without it. Try adding an export to your class (first line). For example:
export class LoginPage extends React.Component {
componentDidMount() {
if (!this.props.reduxReducer.appBootstrapped) {
// make sure that you are using this.props.action() not
// just the action(), which is not connected to redux
this.props.fadeOutAndRemoveSplashScreen(500);
}
}
render() {
return (
<div data-page="login-page" >
<div>This is the login page.</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
reduxReducer: state.reduxReducer
}
};
export default connect(mapStateToProps, {
fadeOutAndRemoveSplashScreen: ReduxActions.fadeOutAndRemoveSplashScreen
})(LoginPage);
Then in your test instead of importing the connected component, import the class:
import ConnectedLoginPage, { LoginPage } from '/path/to/component';
Then simply pass the LoginPage whatever props you want to test with. So we will set your appBooststrapped to false, and then pass the action as a sinon spy:
const spy = sinon.spy();
const reduxReducer = {
appBootstrapped: false, // or true
}
const wrapper = mount(
<LoginPage reduxReducer={reduxReducer} fadeOutAndRemoveSplashScreen={spy} />
);
// test that the spy was called
expect(spy.callCount).to.equal(1);
This makes the test much simpler, and more importantly you are testing the component behavior - not Redux.

Jest -- Mock a function called inside a React Component

Jest provides a way to mock functions as described in their docs
apiGetMethod = jest.fn().mockImplementation(
new Promise((resolve, reject) => {
const userID = parseInt(url.substr('/users/'.length), 10);
process.nextTick(
() => users[userID] ? resolve(users[userID]) : reject({
error: 'User with ' + userID + ' not found.',
});
);
});
);
However these mocks only seem to work when the function is called directly in a test.
describe('example test', () => {
it('uses the mocked function', () => {
apiGetMethod().then(...);
});
});
If I have a React Component defined as such how can I mock it?
import { apiGetMethod } from './api';
class Foo extends React.Component {
state = {
data: []
}
makeRequest = () => {
apiGetMethod().then(result => {
this.setState({data: result});
});
};
componentDidMount() {
this.makeRequest();
}
render() {
return (
<ul>
{ this.state.data.map((data) => <li>{data}</li>) }
</ul>
)
}
}
I have no idea how to make it so Foo component calls my mocked apiGetMethod() implementation so that I can test that it renders properly with data.
(this is a simplified, contrived example for the sake of understanding how to mock functions called inside react components)
edit: api.js file for clarity
// api.js
import 'whatwg-fetch';
export function apiGetMethod() {
return fetch(url, {...});
}
You have to mock the ./api module like this and import it so you can set the implemenation of the mock
import { apiGetMethod } from './api'
jest.mock('./api', () => ({ apiGetMethod: jest.fn() }))
in your test can set how the mock should work using mockImplementation:
apiGetMethod.mockImplementation(() => Promise.resolve('test1234'))
In case the jest.mock method from #Andreas's answer did not work for you. you could try the following in your test file.
const api = require('./api');
api.apiGetMethod = jest.fn(/* Add custom implementation here.*/);
This should execute your mocked version of the apiGetMethod inside you Foo component.
Here is an updated solution for anyone struggling with this in '21. This solution uses Typescript, so be aware of that. For regular JS just take out the type calls wherever you see them.
You import the function inside your test at the top
import functionToMock from '../api'
Then you indeed mock the call to the folder outside of the tests, to indicate that anything being called from this folder should and will be mocked
[imports are up here]
jest.mock('../api');
[tests are down here]
Next we mock the actual function we're importing. Personally I did this inside the test, but I assume it works just as well outside the test or inside a beforeEach
(functionToMock as jest.Mock).mockResolvedValue(data_that_is_returned);
Now here's the kicker and where everyone seems to get stuck. So far this is correct, but we are missing one important bit when mocking functions inside a component: act. You can read more on it here but essentially we want to wrap our render inside this act. React testing library has it's own version of act. It is also asynchronous, so you have to make sure your test is async and also define the destructured variables from render outside of it.
In the end your test file should look something like this:
import { render, act } from '#testing-library/react';
import UserGrid from '../components/Users/UserGrid';
import { data2 } from '../__fixtures__/data';
import functionToMock from '../api';
jest.mock('../api');
describe("Test Suite", () => {
it('Renders', async () => {
(functionToMock as jest.Mock).mockResolvedValue(data2);
let getAllByTestId: any;
let getByTestId: any;
await act(async () => {
({ getByTestId, getAllByTestId } = render(<UserGrid />));
});
const container = getByTestId('grid-container');
const userBoxes = getAllByTestId('user-box');
});
});
Another solution to mock this would be:
window['getData'] = jest.fn();