Jasmine to spyon React component's methods - reactjs

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();

Related

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')
})
})

sinon stub not replacing function in react component

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);

Jasmine Expected Spy to have been called

Here is my angular factory written in typescript:
export class DataService {
constructor () {
this.setYear(2015);
}
setYear = (year:number) => {
this._selectedYear =year;
}
}
Here is my test file.
import {DataService } from ' ./sharedData.Service';
export function main() {
describe("DataService", () => {
let service: DataService;
beforeEach(function () {
service = new DataService();
});
it("should initialize shared data service", () => {
spyOn(service, "setYear");
expect(service).toBeDefined();
expect(service.setYear).toHaveBeenCalled(2015);
});
});
}
When I run the file the test failing saying that
**Expected spy setSelectedCropYear to have been called.
Error: Expected spy setSelectedCropYear to have been called.**
I am not able to figure what is wrong. Can anyone tell me what is wrong with the test please.
The problem is you are setting up the spy too late. By the time you mount the spy on service, it has already been constructed and setYear has been called. But you obviously can not mount the spy on service before it is constructed.
One way around this is to spy on DataService.prototype.setYear. You can make sure it was called by the service instance asserting that
Dataservice.prototype.setYear.calls.mostRecent().object is service.
Fixed the issue here is the updated Test.
import {DataService } from ' ./sharedData.Service';
export function main() {
describe("DataService", () => {
let service: DataService;
beforeEach(function () {
service = new DataService();
});
it("should initialize shared data service", () => {
var spy = spyOn(service, "setYear").and.callThrough();
expect(service).toBeDefined();
expect(spy);
expect(service._selectedYear).toEqual(2015);
});
});
}

Jest : testing simple class with Store dependency

I having a hard time understanding why my mocked method isnt returning the value I specified with mockReturnValue. I am testing a simple method that checks a helper on a store to see if a user is authenticated before proceeding. See connectIfAuth below.
'use strict';
var AuthStore = require('../stores/AuthStore');
console.log('top ' + AuthStore.isAuthenticated());
var TestClass = {
connect() {
//...use a mock
},
connectIfAuth() {
console.log('in connect if: ' + AuthStore.isAuthenticated());
if (AuthStore.isAuthenticated()) {
this.connect();
}
}
};
module.exports = TestClass;
In my test, I want to make the mocked method from the store return true/false and test that the connect() method is called/not-called but the method returns undefined within the method I am testing.
'use strict';
jest.dontMock('../TestClass.js');
var AuthStore = require('../../stores/AuthStore');
describe('TestClass', function() {
var TestClass;
var connectMock;
beforeEach(function() {
//the store should return true
AuthStore.isAuthenticated = jest.genMockFunction().mockReturnValue(true);
//do I have to require this after I mock out the method(s) in AuthStore?
TestClass = require('../TestClass');
connectMock = jest.genMockFunction();
TestClass.connect = connectMock;
});
it('should return the mocked value', function(){
TestClass.connectIfAuth();
expect(connectMock.mock.calls.length).toBe(1); //is 0
});
});
My test output is:
FAIL src/scripts/api/tests/TestClass-tests.js (0.121s)
top undefined
in connect if: undefined
● TestClass › it should return the mocked value
Expected: 0 toBe: 1
at Spec. (/Users/blabla/projects/my-client/src/scripts/api/tests/TestClass-tests.js:20:43)
at Timer.listOnTimeout [as ontimeout] (timers.js:112:15)
In case anyone comes across this same problem - I solved this by putting all of my requires statements outside of the describe/it blocks. Similar to this question, but I also ended up require-ing the module I was testing at the top of the file. I assume I'll have to closely manage the beforeEach and afterEach methods to clean up the mocks I use within my tests.

AngularJS: Testing with FabricJS/Canvas

How do you write tests for something like FabricJS in a directive and service?
Example app: http://fabricjs.com/kitchensink/
I have been trying but I'm not making much progress without really bad hacks.
I want to integrate this service and directive into my https://github.com/clouddueling/angular-common repo so others can use this powerful library.
My scenario:
I'm trying to test my module that contains a service and directive. Those link my app to FabricJS. I'm having issues mocking the global fabric var that is created when you include the js file. I'm assuming then I spy on the var containing the fabric canvas.
I just need to confirm that my service is interacting with fabric correctly. I'm having trouble mocking/stubbing fabric though.
To win the bounty:
Example of a test I could use with Karma.
It's difficult as you've not provided the code you want to test. However, for testability, I would firstly create a very small factory to return the global fabric object
app.factory('fabric', function($window) {
return $window.fabric;
});
This factory can then be tested by injecting a mock $window, and checking that its fabric property is returned.
describe('Factory: fabric', function () {
// load the service's module
beforeEach(module('plunker'));
var fabric;
var fakeFabric;
beforeEach(function() {
fakeFabric = {};
});
beforeEach(module(function($provide) {
$provide.value('$window', {
fabric: fakeFabric
});
}));
beforeEach(inject(function (_fabric_) {
fabric = _fabric_;
}));
it('should return $window.fabric', function () {
expect(fabric).toBe(fakeFabric);
});
});
An example service that then uses this factory is below.
app.service('MyFabricService', function(fabric) {
this.newCanvas = function(element) {
return new fabric.Canvas(element);
}
this.newRectangle = function(options) {
return new fabric.Rect(options);
}
this.addToCanvas = function(canvas, obj) {
return canvas.add(obj);
}
});
You can then test these methods as below. The functions that return 'new' objects can be tested by creating a mock fabric object with a manually created spy that will be called as a constructor, and then using instanceof and toHaveBeenCalledWith to check how its been constructed:
// Create mock fabric object
beforeEach(function() {
mockFabric = {
Canvas: jasmine.createSpy()
}
});
// Pass it to DI system
beforeEach(module(function($provide) {
$provide.value('fabric', mockFabric);
}));
// Fetch MyFabricService
beforeEach(inject(function (_MyFabricService_) {
MyFabricService = _MyFabricService_;
}));
it('should return an instance of fabric.Canvas', function () {
var newCanvas = MyFabricService.newCanvas();
expect(newCanvas instanceof mockFabric.Canvas).toBe(true);
});
it('should pass the element to the constructor', function () {
var element = {};
var newCanvas = MyFabricService.newCanvas(element);
expect(mockFabric.Canvas).toHaveBeenCalledWith(element);
});
The addToCanvas function can be tested by creating a mock canvas object with an 'add' spy.
var canvas;
// Create mock canvas object
beforeEach(function() {
canvas = {
add: jasmine.createSpy()
}
});
// Fetch MyFabricService
beforeEach(inject(function (_MyFabricService_) {
MyFabricService = _MyFabricService_;
}));
it('should call canvas.add(obj)', function () {
var obj = {};
MyFabricService.addToCanvas(canvas, obj);
expect(canvas.add).toHaveBeenCalledWith(obj);
});
This can all be seen in action in this Plunker http://plnkr.co/edit/CTlTmtTLYPwemZassYF0?p=preview
Why would you write tests for an external dependency?
You start by assuming FabricJS just works. It's not your job to test it, and even if it were, you'd have to do byte stream comparison (that's what a canvas is, a stream of bytes interpreted as an image). Testing user input is a whole different thing. Look up Selenium.
Then you write tests for the code that produces the correct input for FabricJS.

Resources