Testing an isolated function of a component in React Jest - javascript

I am fairly new to Jest and Enzyme and I stumbled a cross a problem:
I have a Component which renders Children and also calls a method of those children. I achieve that by using refs. I call these functions something like:
somefunction = () => {
this.myReference.current.childFunction();
this.doSomethingOther();
}
I now want to test the function somefunction. I want to check if the doSomethingOther function was called. Using a shallow render I cannot achieve that. The test would succeed if this.myReference.current.childFunction(); wasn't called. Jest cannot know it because it only renders shallow and therefore throws an error.
I may be missing the full understanding. I wonder if someone has a Idea to test this function without using mount.

Take a look at the below code where I shallow render a component and then get the class instance and mock the required functions. Now when we call somefunction, we check if doSomethingOther has been called. (Assumption you are using jest+enzyme)
const wrapper = shallow(<Comp />);
const inst = wrapper.instance();
inst.myReference = {
current: {
childFunction: jest.fn()
}
}
inst.doSomethingOther = jest.fn();
inst.somefunction();
expect(inst.doSomethingOther).toHaveBeenCalled();

Related

How to test if a function is called when I submit a form with sinon?

intro:
I want to test that if I click on the submit button, the onSubmit function is called. I assume this is possible from what I understand when I read the documentation:
https://sinonjs.org/releases/v6.1.5/spies/
https://vue-test-utils.vuejs.org/guides/#testing-key-mouse-and-other-dom-events
expected output:
get the test to run and show me either pass or fail
actual output:
none, I'm currently stuck at the following:
context:
in my test:
import NavBar from '#/components/layout/NavBar.vue'
in that component I have a (simplified version here) form:
<b-nav-form #submit="onSubmit">
<b-form-input />
<b-button type="submit">Search</b-button>
</b-nav-form>
I want to test that if I click on the submit button, the onSubmit function is called.
My setup is Vue, BootstrapVue and Sinon. I understand I have to setup a spy that listens to a function being called.
This is the actual script in my component if that is helpful:
<script>
export default {
data () {
return {
query: ''
}
},
methods: {
onSubmit () {...}
}
}
</script>
example that I understand:
it('a true example', () => {
var f = {
onSubmit: function(query) {
this.query = query;
}
}
var onSubmitSpy = sinon.spy(f, 'onSubmit');
f.onSubmit('Club')
console.log(onSubmitSpy.callCount); // is 1
onSubmitSpy.restore();
})
But this is not connected to for example clicking on the button in the form.
Please advise
The idea to test functions of vue components to have been called is to:
Create testing components with vue-test-utils mount or shallowMount.
Pass a methods param in the options to provide spies.
Perform actions in the component that calls the spied method, then assert the method was really called.
I don't have sinon experience, am only used to test vue components with jest, but the thing should be something like the following:
import NavBar from '#/components/layout/NavBar.vue'
import {shallowMount} from 'vue-test-utils';
it('asserting onSubmit calls', () => {
// given
var onSubmit = sinon.spy();
var wrapper = shallowMount(NavBar, {
methods: {
onSubmit();
}
});
var vm = wrapper.vm;
// when
vm.onSubmit();
// then (I dont really dont know if this is valid sinon syntax)
assertTrue(onSubmit.called);
// or, with jest syntax:
// expect(onSubmit).toHaveBeenCalled();
})
Now, the code snippet should work, but there are problems with this test: we are asserting that when we call the component onSubmit, the onSubmit spy gets called. This is not the great thing.
Your test would probably need to assert somehing like: when the <b-nav-form> component emits a submit event, then the onSubmit spy gets called.
That would be a more complex test, for two reasons:
Because a child component is involved. To really render child components in a vue-test-utils test, you need to use mount instead of shallowMount. This is difficult as you need to provided childs props and dependencies, so get used to the shallowMount and mount differences.
When you start testing events, chances are some synchrony is involved, so you need to wait for the event to propagate and get your component method called. This usually involves to pass done callback to it() blocks.

Testing with Jest a method in React that calls another irrelevant method to the test

Learning unit-testing, I am trying to use Jest to test React components. But i ran into an error while testing a method of a component that calls another method, that resides into another project entirely and then the parent method changes the state of it's component, so everything on the other method that is being called is normally undefined, therefore resulting on this test error. Can anyone guide me how to approach this problem? Supposing that the method being tested is:
methodToBeTested() {
methodResidingIntoAnotherProject();
this.setState({someState: true}); // someState is initially false
}
And the testing:
describe("testing the component's behaviour"), () => {
it("testing the methodToBeTested", () => {
const {wrapper} = setup();
wrapper.setState({someState: false});
wrapper.instance().methodToBeTested();
expect(wrapper.state().someState).toEqual(true);
});
});
The wrapper and the whole setup works, because i have tried some other structural tests, that have passed. The error that is being thrown is:
"TypeError: Cannot read property 'someVariable' of undefined". The 'someVariable' is in the nested method.

How to mock an asynchronous function call in another class

I have the following (simplified) React component.
class SalesView extends Component<{}, State> {
state: State = {
salesData: null
};
componentDidMount() {
this.fetchSalesData();
}
render() {
if (this.state.salesData) {
return <SalesChart salesData={this.state.salesData} />;
} else {
return <p>Loading</p>;
}
}
async fetchSalesData() {
let data = await new SalesService().fetchSalesData();
this.setState({ salesData: data });
}
}
When mounting, I fetch data from an API, which I have abstracted away in a class called SalesService. This class I want to mock, and for the method fetchSalesData I want to specify the return data (in a promise).
This is more or less how I want my test case to look like:
predefine test data
import SalesView
mock SalesService
setup mockSalesService to return a promise that returns the predefined test data when resolved
create the component
await
check snapshot
Testing the looks of SalesChart is not part of this question, I hope to solve that using Enzyme. I have been trying dozens of things to mock this asynchronous call, but I cannot seem to get this mocked properly. I have found the following examples of Jest mocking online, but they do not seem to cover this basic usage.
Hackernoon: Does not use asychronous calls
Wehkamp tech blog: Does not use asynchronous calls
Agatha Krzywda: Does not use asynchronous calls
GitConnected: Does not use a class with a function to mock
Jest tutorial An Async Example: Does not use a class with a function to mock
Jest tutorial Testing Asynchronous Code: Does not use a class with a function to mock
SO question 43749845: I can't connect the mock to the real implementation in this way
42638889: Is using dependency injection, I am not
46718663: Is not showing how the actual mock Class is implemented
My questions are:
How should the mock class look like?
Where should I place this mock class?
How should I import this mock class?
How do I tell that this mock class replaces the real class?
How do set up the mock implementation of a specific function of the mock class?
How do I wait in the test case for the promise to be resolved?
One example that I have that does not work is given below. The test runner crashes with the error throw err; and the last line in the stack trace is at process._tickCallback (internal/process/next_tick.js:188:7)
# __tests__/SalesView-test.js
import React from 'react';
import SalesView from '../SalesView';
jest.mock('../SalesService');
const salesServiceMock = require('../SalesService').default;
const weekTestData = [];
test('SalesView shows chart after SalesService returns data', async () => {
salesServiceMock.fetchSalesData.mockImplementation(() => {
console.log('Mock is called');
return new Promise((resolve) => {
process.nextTick(() => resolve(weekTestData));
});
});
const wrapper = await shallow(<SalesView/>);
expect(wrapper).toMatchSnapshot();
});
Sometimes, when a test is hard to write, it is trying to tell us that we have a design problem.
I think a small refactor could make things a lot easier - make SalesService a collaborator instead of an internal.
By that I mean, instead of calling new SalesService() inside your component, accept the sales service as a prop by the calling code. If you do that, then the calling code can also be your test, in which case all you need to do is mock the SalesService itself, and return whatever you want (using sinon or any other mocking library, or even just creating a hand rolled stub).
You could potentially abstract the new keyword away using a SalesService.create() method, then use jest.spyOn(object, methodName) to mock the implementation.
import SalesService from '../SalesService ';
test('SalesView shows chart after SalesService returns data', async () => {
const mockSalesService = {
fetchSalesData: jest.fn(() => {
return new Promise((resolve) => {
process.nextTick(() => resolve(weekTestData));
});
})
};
const spy = jest.spyOn(SalesService, 'create').mockImplementation(() => mockSalesService);
const wrapper = await shallow(<SalesView />);
expect(wrapper).toMatchSnapshot();
expect(spy).toHaveBeenCalled();
expect(mockSalesService.fetchSalesData).toHaveBeenCalled();
spy.mockReset();
spy.mockRestore();
});
One "ugly" way I've used in the past is to do a sort of poor-man's dependency injection.
It's based on the fact that you might not really want to go about instantiating SalesService every time you need it, but rather you want to hold a single instance per application, which everybody uses. In my case, SalesService required some initial configuration which I didn't want to repeat every time.[1]
So what I did was have a services.ts file which looks like this:
/// In services.ts
let salesService: SalesService|null = null;
export function setSalesService(s: SalesService) {
salesService = s;
}
export function getSalesService() {
if(salesService == null) throw new Error('Bad stuff');
return salesService;
}
Then, in my application's index.tsx or some similar place I'd have:
/// In index.tsx
// initialize stuff
const salesService = new SalesService(/* initialization parameters */)
services.setSalesService(salesService);
// other initialization, including calls to React.render etc.
In the components you can then just use getSalesService to get a reference to the one SalesService instance per application.
When it comes time to test, you just need to do some setup in your mocha (or whatever) before or beforeEach handlers to call setSalesService with a mock object.
Now, ideally, you'd want to pass in SalesService as a prop to your component, because it is an input to it, and by using getSalesService you're hiding this dependency and possibly causing you grief down the road. But if you need it in a very nested component, or if you're using a router or somesuch, it's becomes quite unwieldy to pass it as a prop.
You might also get away with using something like context, to keep everything inside React as it were.
The "ideal" solution for this would be something like dependency injection, but that's not an option with React AFAIK.
[1] It can also help in providing a single point for serializing remote-service calls, which might be needed at some point.

How to test child component method with Enzyme?

I have a component like that:
<Parent>
<Child/>
</Parent>
and <Child/> component have a method foo. I want test the foo method but I don't know how to access it. I tried:
mount(<Parent><Child/></Parent>).props().children.foo
or
mount(<Parent><Child/></Parent>).children().foo
but both them are undefined. I can't use .instance() because it's not root. I can't mount <Child/> only because the <Parent> add something (react-router's context.router) on context and I need them when init <Child/>. Any idea with this?
This worked for me:
mount(<Parent><Child/></Parent>).find(Child).instance().foo
I would consider writing tests for only your parent class, and then a separate test file to only test your child.
Once you have mounted you component using:
const component = mount(<Child>);
you then have access to it's methods using:
component.instance().methodname
You can then do stuff like override it with jest.fn() and test appropriately.
I prefer shallow mount over full mount from enzyme.
In conjunction with proxyquire to resolve child component (which you want to test)
I do
wrapper.find('Child1').props().propName
And test it.
Or I use shallow
mount wrapper.dive()
I think your problem is way different from how to test child components.
My first question is: Why are you checking if a child component has a specific method in the parent's component tests?
IMHO you need to have a test specific for this component and, then, in this test you check if the method exists.
Just to not leave without the answer, did you tried .find(Child).instance().foo ?
I had a similar problem when trying to mock a function on an inner component within a MemoryRouter:
cont wrapper = mount(<MemoryRouter><AvailabilityButtonWithRouter.WrappedComponent vetId={ vetId } appointment={ availability } /></MemoryRouter>);
I ended up being able to mock the function like so:
const mockFn = jest.fn();
wrapper.children(0).children(0).instance().reloadCurrentPage = mockFn;
I was able to get a handle on child function like the following, i was looking for the first child to call the function on -
const component = shallow(<Component />);
component.find(Child).first().getNode().props.someChildFunction()
I faced a similar problem and I went through mount API by logging. In my use case, my child component(CommonStoresReactions) is wrapped with mobx inject.
const jsx = (
<Provider {...stores}>
<CommonStoresReactions>
<div />
</CommonStoresReactions>
</Provider>
)
const wrapper = mount(jsx)
I want to test clearStores method in CommonStoresReactions. Below snippet worked for me.
wrapper
.find(CommonStoresReactions)
.instance()
.wrappedInstance.clearStores()
Enzyme has an option for the mount API called wrappingComponent (and wrappingComponentProps) to wrap the mounted object inside another component for providing context, etc.
See https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#mountnode-options--reactwrapper
I managed to solve this by using dive
wrapper.dive().props().propName
With enzyme:
mount(<Parent><Child/></Parent>).childAt(0).instance().foo
There are valid reasons to access the child and call a method. If the parent is superficial and children have a consistent interface you can call methods without knowing which child it is, testing that all children have the correct interface, signature etc.
The best way I find out is using shallow wrapper's dive method. Here is the doc: enzyme dive doc
Remember if ur parent component use the fully rendering like mount, then the react wrapper itself doesnt have the dive method so u have to use shallow render.
Here is one example:
let instance, child, diveChild;
describe('Test Parent child Child component', () => {
beforeEach(() => {
wrapper = shallow(<Parent {...props} />);
child = wrapper.find('Child');
diveChild = child.dive();
console.log(diveChild.instance());
});
test('Child get mounted', () => {
expect(child.exists()).toBeTruthy();
expect(child.debug()).toMatchSnapshot();
});
});
I would start by echoing #rnmalone's answer that you probably don't want to test a function on a child directly. That's not really unit testing, that's integration testing. You don't want to test your foo method.
That said, you may want to grab children to test their bar value and see if they received something you did to them by manipulating the parent. And since, if you mount at least (?), the children are fully instantiated, there's no reason to spy on a shim; you can go straight to the "real" child and test it.
Here's a test that does both. We do test a foo on a child, so to speak -- it's an event handler -- and then we test its bar value -- in this case that the proper child had a value set to match what we raised in the foo event.
Testing if foo sets bar
In this test, we've spun up a component we're testing (we were using Preact instead of React and htm in place of JSX; apologies if I don't clean this perfectly):
var wrapper = Enzyme.mount(
<MyParentComponent
myItemTypes={arrayTypes}
mySelectedItem={someSelectedItem}
onTabClicked={mySpy.onTabClicked}
/>
);
Now MyParentComponent has child components in its markup called MyChildComponent.
(This is its "live" code, returned by a functional component, and is not from a test file, to be overly clear.)
return <ul>
{Object.keys(props.myItemTypes).map(function (key) {
var isSelected = myItemTypes[key] === mySelectedItem;
return
<MyChildComponent
isSelected={isSelected}
tabType={myItemTypes[key]}
onTabClicked={props.onTabClicked}
></MyChildComponent>
;
})}
</ul>;
So the parent component is, with respect to the onTablClicked event handler, basically just a passthrough.
Now I can spoof a click on a child item like this using ReactTestUtils:
var specificItem = wrapper
.find('MyChildComponent')
.findWhere((x) => x.props().tabType.value === newlySelectedTab.value);
if (specificItem) {
var onTabClicked = lessonsTab.props().onTabClicked;
TestUtils.act(function () {
onTabClicked(newlySelectedTab);
});
}
wrapper.update();
The nasty part here is that I registered that onTabClicked from this on the parent component:
onTabClicked={mySpy.onTabClicked}
That is, that act on the selected child will just call my spy, and my spy does this:
spyOn(mySpy, 'onTabClicked').and.callFake(function (tab) {
wrapper.setProps({ mySelectedItem: tab });
});
That's problematic. We'll discuss that later.
Now I can run a test to see if the child's prop was updated.
it('should send the new selection value to the child component', function () {
var allItems = wrapper.find(MyChildComponent);
var selectedItem = navItems.findWhere((x) => x.props().isSelected);
expect(selectedItem.props().settingsTab.value).toBe(newlySelectedTab.value);
});
But that really reduces to mock foo and real bar
Again, the weird part of doing that is the fake click on the child is really close to testing your mocks, which you shouldn't really do. (Reference is a powerpoint. Click at your own risk.)
I could've just tested initial state setting like this:
it('should send the new selection value to the child component', function () {
// Act
wrapper.setProps({ mySelectedItem: itemToSelect });
wrapper.update();
// Assert
var allItems = wrapper.find(MyChildComponent);
var selectedItem = navItems.findWhere((x) => x.props().isSelected);
expect(selectedItem.props().tabType.value).toBe(itemToSelect.value);
});
... and that's probably good enough for whatever work you're doing. It reduces to nearly the same thing.
I guess the bonus is that you know the event handler is registered on the child, and that's something. Though we should probably just test that foo fired using mySpy on MyParentComponent, right? And then add a second test to see that the child value changes when the parent prop does.
So likely still a code smell somewhere.

React - componentWillReceiveProps does not execute when component rendered in unit test

New to React, so apologize in advance if this is an ignorant question. I'm using componentWillReceiveProps to execute a function post-render. The function is passed in as a prop. Trying to write a unit test to show that the passed in function gets called, but componentWillReceiveProps does not seem to get called when I render the component in my test. It works when I run the app in the browser, but not in the test.
Here's the test:
it('should call function \'selectAll\' when isDefaultSelectAll is true and component is rendered.', ()=> {
const selectAllMock = jest.genMockFunction();
const isDefaultSelectAll = true;
component = TestUtils.renderIntoDocument(<MyComponent {isDefaultSelectAll, selectAllMock} />);;
expect(selectAllMock.mock.calls.length).toBe(1);
});
Here's the code being tested:
componentWillReceiveProps(nextProps) {
console.log('GOT HERE');
if (nextProps.isDefaultSelectAll === true) {
nextProps.selectAll();
}
}
Even the log statement does not seem to be hit, and I'm not sure why. Googling has not yielded an answer.
Edit for the first answer - attempted to do a second render as follows, but still not having any luck:
const isDefaultSelectAll = false;
component = TestUtils.renderIntoDocument(<MyComponent {isDefaultSelectAll, selectAllMock} />);
isDefaultSelectAll = true;
component = TestUtils.renderIntoDocument(<MyComponent {isDefaultSelectAll, selectAllMock} />);
componentWillReceiveProps is not called on the initial render, as stated in the docs:
https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops
Either try to use another lifecycle method or re-render the component in your test.

Categories

Resources