sinon stub not replacing function in react component - reactjs

I'm trying to test a click on a react component uszing Enzyme + Sinon
var stub = sinon.stub(Comp.prototype, 'save', function() { });
let wrapper = shallow(
<Comp/>
);
wrapper.find('.btn-header').simulate('click');
sinon.assert.called(stub);
Comp.prototype.refineOnClick.restore();
My Comp component has a save function that throws an exception
save: function () {
throw('error');
}
When I run the test, I expect no errors to be thrown and the empty function in the stub to fire - but it doesn't. The actual function inside the component is fired and not the empty stub.

You can access (and therefore stub) functions on your enzyme wrapper by using it's instance. So to test your component's save function, you do the following:
const wrapper = shallow(<Comp />)
sinon.stub(wrapper.instance(), 'save')
wrapper.find('.btn-header').simulate('click')
expect(wrapper.instance().save).to.have.been.called
Note, I'm using sinon-chai for the .to.have.been.called syntax.

One of the principles of unit testing is, that you shouldn't fake internals of unit under test. It just makes tests less readable and maintainable. Method save is obviously internal to Comp and thus shouldn't be faked.
It would be OK if you would pass it as parameter into that component:
var stub = sinon.stub();
let wrapper = shallow(
<Comp onSave={stub} />
);
wrapper.find('.btn-header').simulate('click');
sinon.assert.called(stub);

Related

How to mock and check that a function from a library in Jest framework?

I have a button with "Download" text on it defined in ReactJS code. Now, I want to write a unit test to check that this function is getting called when this button is clicked. I wrote a unit test but it is not working.
import * as FileSaver from "file-saver"
it('File has to be saved when clicked on the "Download" button', () => {
jest.mock('file-saver', ()=>({saveAs: jest.fn()}));
fireEvent.click(component.getByText("Download"));
expect(FileSaver.saveAs).toBeCalled();
})
I'm getting this error:
Error: expect(received).toBeCalled()
Matcher error: received value must be a mock or spy function
Received has type: function
Received has value: [Function anonymous]
pointing to expect(FileSaver.saveAs).toBeCalled(); line.
What's wrong?
So, as I stated in the comment, you have to move jest.mock('file-saver', ()=>({saveAs: jest.fn()})) from the body of the test to the top of the file, just under the imports. The reason for it is actually answered in the documentation here, but to wrap it up:
In your test file you are using import statement which fires up at start, before any code has a chance to run. Then you try to mock file-saver, but it is already imported with real implementation, not mocked. If you instruct jest to mock module at top of the file it will automatically hoist jest.mock calls to the top of the module so your function exportToExcel will receive mocked file-saver instead of the real one.
But if you really want to mock file-saver in the body of the test for some strange reason you would need to mock file-saver, then include all modules in test which use file-saver, something like this:
it('File has to be saved when clicked on the Export button', () => {
jest.mock('file-saver', ()=> ({ saveAs: jest.fn() }));
const component = require('/path/to/tested/componetn/on/which/click/event/is/fired');
const FileSaver = require('file-saver');
fireEvent.click(component.getByText("Download"));
expect(FileSaver.saveAs).toBeCalled();
})

React - Testing controller component with jest/enzyme

I'm in bit of a dead-end as I'm not sure how I should go about testing this specific "component". So basically, I have a controller component which is a top-level component. It doesn't take in any props, and it is rendered by a route component. The controller component has several functions, which some are passed into a child component and are triggered by event handlers.
Additionally, the controller component uses an API that is attached to the global window object. The API takes in a callback function which then will be called when you call certain methods on the API, after the methods have been run. Right now, I have no idea how I should try to test the controller. I have tested all child components and verified that everything works, but some of these functions within the controller component would be crucial to test that they actually do work.
const MyController = () => {
const [api, setApi] = useState(null)
useEffect(() => {
const globalApi = window.globalApi
setApi(globalApi)
init()
}, [])
function callBack(e) {
console.log(e)
}
function init() {
api.init(callBack)
}
function close() {
api.close()
}
return (
<MyComponent
close={close}
/>
)
}
Mock your api and see if its called
You can spy on your api call from global (if I read your code correctly). Then you can mock implement it.
const apiCall = jest.spyOn(global.globalApi, 'init').mockImplementation(jest.fn);
expect(apiCall).toHaveBeenCalled();
There's a couple of tests you can do. Check how many times its called, should be once for you. and check what arguments it was called with.
Note
The use of global in the spy. global refers to the window.
Extra tests
Beyond these tests I would suggest making a snap shot of a shallow render, just to make sure the render is always working.

How to properly test react component methods

I've been developing in React for a while for my work, but recently I was requested to get some applications to ~100% test coverage using Istanbul. I've wrote over 160 tests for this application alone in the past few days, but I haven't been able to cover certain parts of my code. Im having the most trouble covering AJAX calls, setTimeout callbacks, and component methods that require another component to operate properly.
I've read several SO questions to no avail, and I believe that is because I'm approaching this incorrectly. I am using Enzyme, Chai assertions, Mocha, Istanbul coverage, sinon for spies, and was considering nock since I cant get sinon fakeServer working.
Here is the component method in question:
_getCategoriesFromServer() {
const _this = this;
sdk.getJSON(_this.props.sdkPath, {
itemsperpage: 10000
}).done(function(data) {
_this.setState({
isLoaded: true,
categories: _this.state.categories.concat(data)
});
});
}
Here is the test for that component:
it('should call _getCategoriesFromServer', () => {
sinon.spy(CategoryTree.prototype, '_getCategoriesFromServer');
wrapper = mount(<CategoryTree {...props} />);
expect(CategoryTree.prototype._getCategoriesFromServer.calledOnce).to.be.true;
});
The sdk is just a module that constructs a jQuery API call using getJSON.
My test is covering the function call, but its not covering the .done callback seen here:
So my question is, how can I properly test the .done?
If anyone has an article, tutorial, video, anything that explains how to properly test component methods, I would really appreciate it!
Second question is, how can I go about testing a method that gets passed down as a prop to a child component? With the testing coverage requirement I have to have that method tested, but its only purpose is to get passed down to a child component to be used as an onClick. Which is fine, but that onClick is dependent on another AJAX call returning data IN the child component.
My initial impulse was to just use enzymes .find to locate that onClick and simulate a click event, but the element with that onClick isn't there because the AJAX call didn't bring back data in the testing environment.
If you've read this far, I salute you. And if you can help, I thank you!
You could use rewire(https://github.com/jhnns/rewire) to test your component like this:
// let's said your component is ListBox.js
var rewire = require("rewire");
var myComponent = rewire("../components/ListBox.js");
const onDone = sinon.spy()
const sdkMock = {
getJSON (uri, data) {
return this.call('get', uri, data);
},
call: function (method, uri, data) {
return { done: function(){ onDone() } }
}
};
myComponent.__set__("sdk", sdkMock);
and finally you will test if the done function get called like this:
expect(onDone.calledOnce)to.be.true
With this should work as expected. If you need more options you could see all the options of rewire in GitHub.
BABEL
If you are using babel as transpiler you need to use babel-plugin-rewire(https://github.com/speedskater/babel-plugin-rewire) you could use it like this:
sdk.js
function call(method, uri, data) {
return fetch(method, uri, data);
}
export function getJSON(uri, data) {
return this.call('get', uri, data);
}
yourTest.js
import { getJSON, __RewireAPI__ as sdkMockAPI } from 'sdk.js';
describe('api call mocking', function() {
it('should use the mocked api function', function(done) {
const onDone = sinon.spy()
sdkMockAPI.__Rewire__('call', function() {
return { done: function(){ onDone() } }
});
getJSON('../dummy.json',{ data: 'dummy data'}).done()
expect(onDone.calledOnce)to.be.true
sdkMockAPI.__ResetDependency__('call')
})
})

mocha JSDOM testing on react, getElementById return null

I'm using mocha and JsDom to test my react component.
First of all my component works perfectly, so it is problem with the testing enviornment.
Situation:
I have a component that render a couple of select tag with id. Then a componentDidMount in the component that would use document.getElementById to get those select tags and add options to them. But when I run my test, it is showing null for these getElementById.
Now if I comment out componentDidMount, and assert stuff such as below, it works perfectly, so the component did render out those select tags.
describe('test component', function(){
var renderedElement = ReactTestUtils.renderIntoDocument(<Component/>);
var renderedNode = ReactDom.findDOMNode(renderedElement);
it('should have the proper markup', function(){
assert.equal(renderedNode.childElementCount, 5);
[...]
})
})
what is causing the problem? Is it cause document.getElementById the document object doesn't exist in my testing environment cause Im using a 'fake' one, if so how should I test this?
below is my jsdom setup for mocha
(function () {
'use strict';
var jsdom = require('jsdom'),
baseHTML,
window;
if (!global.window) {
baseHTML = '<!DOCTYPE html><html><head lang="en"><meta charset="UTF-8"><title>Tests</title></head><body></body></html>';
window = jsdom.jsdom(baseHTML).defaultView;
global.window = window;
global.document = window.document;
global.navigator = window.navigator;
}
}());
So, the canonical way to get access to the mounted DOM component in React is to use a ref. Instead of using document.getElementById() in your componentDidMount instead use a ref={(element) => { /* Do something with element like this.element = element to save it */ }} on the element as you render it. This will get a correct callback with the mounted DOM element without the need to reference document.
Usage of document should be avoided if possible in React code as it makes it impossible to do universal rendering.

Jasmine to spyon React component's methods

I have a React component class with a couple of methods -
var testClass = React.createClass({
testMethod: function(){//do something
}
componentDidMount: function(){
this.testMethod();
}
render: function() {
...
}
});
I am trying to add a unit test using jasmine
import TestClass from './testClass';
describe('test functions', () => {
'use strict';
spyOn(TestClass.prototype, 'testMethod');
it('test calls testMethod', () => {
component = TestUtils.renderIntoDocument(<TestClass />);
expect(TestClass.prototype.testMethod).toHaveBeenCalled();
});
I can see in the debugger that the testMethod is really getting called but jasmine reports "Expected spy testMethod to have been called."
React v0.14.2, createClass maintains an object map of defined methods in __reactAutoBindMap and reuses this map to rebind the methods to the component in the the private constructor for the component.
This behavior can be worked around in the tests by resetting this map to an empty object, forcing the constructor to skip this map and use the replacement spy.
function mockComponentMethod(component, method) {
const boundMethod = method.bind(component)
component.prototype.__reactAutoBindMap = {};
component.prototype[method.name] = boundMethod;
return boundMethod;
}
The test to check that the spy was called can be written as follows:
const testMethodSpy = spyOn(TestClass.prototype, 'testMethod');
mockComponentMethod(TestClass, testMethodSpy);
expect(testMethodSpy).toHaveBeenCalled();

Resources