Stubbing a React component method with Sinon - javascript

I'm trying to stub a React component method for testing purpose:
var Comp = React.createClass({
displayName: "Comp",
plop: function() {
console.log("plop");
},
render: function() {
this.plop();
return React.DOM.div(null, "foo");
}
});
var stub = sinon.stub(Comp.type.prototype, "plop");
React.addons.TestUtils.renderIntoDocument(Comp());
sinon.assert.called(stub); // throws
This sadly keeps printing "plop" onto the console… and the assertion fails.
Note: Directly stubbing the spec object method works, but then you have to export the component constructor and the spec separately so they're both available in tests… Also, you'd need to stub the spec before even creating the component class; not so convenient:
var CompSpec = {
displayName: "Comp",
plop: function() {
console.log("plop");
},
render: function() {
this.plop();
return React.DOM.div("foo");
}
};
var stub = sinon.stub(CompSpec, "plop");
var Comp = React.createClass(CompSpec);
React.addons.TestUtils.renderIntoDocument(Comp());
// plop() is properly stubbed, so you can
sinon.assert.called(stub); // pass
Can you think of another strategy to easily stub a React component method?

You're running up against React's auto-binding feature, which caches the .bind(this) which is wrapped around your class methods. You can get your code to work by stubbing the cached version of the method in React's __reactAutoBindMap:
var Comp = React.createClass({
displayName: "Comp",
plop: function() {
console.log("plop");
},
render: function() {
this.plop();
return React.DOM.div(null, "foo");
}
});
// with older versions of React, you may need to use
// Comp.type.prototype instead of Comp.prototype
var stub = sinon.stub(Comp.prototype.__reactAutoBindMap, "plop"); // <--
React.addons.TestUtils.renderIntoDocument(React.createElement(Comp));
sinon.assert.called(stub); // passes

Which test framework are you using?
If you use jasmine, I've found jasmine-react to be a useful library for spying on React methods as well as replacing Components with test stubs.
In this case, you can spy on your method easily outside component definition.
//Component Definition
var Comp = React.createClass({
displayName: "Comp",
plop: function() {
console.log("plop");
},
render: function() {
this.plop();
return React.DOM.div(null, "foo");
}
});
//test
it("should call plop method on render", function(){
//spy on method
jasmineReact.spyOnClass(Comp, "plop");
React.addons.TestUtils.renderIntoDocument(Comp());
expect(Comp.plop).toHaveBeenCalled();
})
jasmineReact.spyOnClass returns an ordinary jasmine spy that you can use to track calls to it and its arguments.
If you want to actually stub the method and make it return something, you can do something like
jasmineReact.spyOnClass(Comp, "plop").andReturn('something')
Alternatively Facebook have recently launched a test framework Jest (also has jasmine as a dependency) which they use themselves for testing React components. Component methods can easily be stubbed using this framework. This looks like its worth checking out as well, but probably comes into its own a bit more when you write your components inside commonJS modules

Related

How can I check that a class constructor is called with proper attributes in Sinon?

I'm testing code that instantiates an object from an external library. In order to make this testable, I've decided to inject the dependency:
Boiled down to:
const decorator = function (obj, _extLib) {
var ExtLib = _extLib || require('extlib')
config = determineConfig(obj) //This is the part that needs testing.
var el = new ExtLib(obj.name, config)
return {
status: el.pay({ amt: "one million", to: "minime" })
bar: obj.bar
}
}
In my test, I need to determine that the external library is instantiated with the proper config. I'm not interested in whether this external library works (it does) nor wether calling it, gives results. For the sake of the example, let's assume that on instantiating, it calls a slow bank API and then locks up millions of dollars: we want it stubbed, mocked and spied upon.
In my test:
it('instantiates extLib with proper bank_acct', (done) => {
class FakeExtLib {
constructor(config) {
this.acct = config.bank_acct
}
this.payMillions = function() { return }
}
var spy = sandbox.spy(FakeExtLib)
decorator({}, spy) // or, maybe decorator({}, FakeExtLib)?
sinon.assert.calledWithNew(spy, { bank_acct: "1337" })
done()
})
Do note that testing wether e.g. el.pay() was called, works fine, using spies, in sinon. It is the instantiation with new, that seems untestable.
To investigate, let's make it simpler even, testing everything inline, avoiding the subject under test, the decorator function entirely:
it('instantiates inline ExtLib with proper bank_acct', (done) => {
class ExtLib {
constructor(config) {
this.acct = config.bank_acct
}
}
var spy = sandbox.spy(ExtLib)
el = new ExtLib({ bank_acct: "1337" })
expect(el.acct).to.equal("1337")
sinon.assert.calledWithNew(spy, { bank_acct: "1337" })
done()
})
The expect part passes. So apparently it is all called properly. But the sinon.assert fails. Still. Why?
How can I check that a class constructor is called with proper attributes in Sinon?" Is calledWithNew to be used this way? Should I spy on another function such as the ExtLib.prototype.constructor instead? If so, how?
You're really close.
In the case of your simplest example, you just need to create el using the spy instead of ExtLib:
it('instantiates inline ExtLib with proper bank_acct', (done) => {
class ExtLib {
constructor(config) {
this.acct = config.bank_acct
}
}
var spy = sandbox.spy(ExtLib)
var el = new spy({ bank_acct: "1337" }) // use the spy as the constructor
expect(el.acct).to.equal("1337") // SUCCESS
sinon.assert.calledWithNew(spy) // SUCCESS
sinon.assert.calledWithExactly(spy, { bank_acct: "1337" }) // SUCCESS
done()
})
(Note that I modified the test to use calledWithExactly to check the arguments since calledWithNew doesn't seem to check the arguments properly in v7.2.2)

Mock Es6 classes using Jest

I'm trying to mock an ES6 class with a constructor that receives parameters, and then mock different class functions on the class to continue with testing, using Jest.
Problem is I can't find any documents on how to approach this problem. I've already seen this post, but it doesn't resolve my problem, because the OP in fact didn't even need to mock the class! The other answer in that post also doesn't elaborate at all, doesn't point to any documentation online and will not lead to reproduceable knowledge, since it's just a block of code.
So say I have the following class:
//socket.js;
module.exports = class Socket extends EventEmitter {
constructor(id, password) {
super();
this.id = id;
this.password = password;
this.state = constants.socket.INITIALIZING;
}
connect() {
// Well this connects and so on...
}
};
//__tests__/socket.js
jest.mock('./../socket');
const Socket = require('./../socket');
const socket = new Socket(1, 'password');
expect(Socket).toHaveBeenCalledTimes(1);
socket.connect()
expect(Socket.mock.calls[0][1]).toBe(1);
expect(Socket.mock.calls[0][2]).toBe('password');
As obvious, the way I'm trying to mock Socket and the class function connect on it is wrong, but I can't find the right way to do so.
Please explain, in your answer, the logical steps you make to mock this and why each of them is necessary + provide external links to Jest official docs if possible!
Thanks for the help!
Update:
All this info and more has now been added to the Jest docs in a new guide, "ES6 Class Mocks."
Full disclosure: I wrote it. :-)
The key to mocking ES6 classes is knowing that an ES6 class is a function. Therefore, the mock must also be a function.
Call jest.mock('./mocked-class.js');, and also import './mocked-class.js'.
For any class methods you want to track calls to, create a variable that points to a mock function, like this: const mockedMethod = jest.fn();. Use those in the next step.
Call MockedClass.mockImplementation(). Pass in an arrow function that returns an object containing any mocked methods, each set to its own mock function (created in step 2).
The same thing can be done using manual mocks (__mocks__ folder) to mock ES6 classes. In this case, the exported mock is created by calling jest.fn().mockImplementation(), with the same argument described in (3) above. This creates a mock function. In this case, you'll also need to export any mocked methods you want to spy on.
The same thing can be done by calling jest.mock('mocked-class.js', factoryFunction), where factoryFunction is again the same argument passed in 3 and 4 above.
An example is worth a thousand words, so here's the code.
Also, there's a repo demonstrating all of this, here:
https://github.com/jonathan-stone/jest-es6-classes-demo/tree/mocks-working
First, for your code
if you were to add the following setup code, your tests should pass:
const connectMock = jest.fn(); // Lets you check if `connect()` was called, if you want
Socket.mockImplementation(() => {
return {
connect: connectMock
};
});
(Note, in your code: Socket.mock.calls[0][1] should be [0][0], and [0][2] should be [0][1]. )
Next, a contrived example
with some explanation inline.
mocked-class.js. Note, this code is never called during the test.
export default class MockedClass {
constructor() {
console.log('Constructed');
}
mockedMethod() {
console.log('Called mockedMethod');
}
}
mocked-class-consumer.js. This class creates an object using the mocked class. We want it to create a mocked version instead of the real thing.
import MockedClass from './mocked-class';
export default class MockedClassConsumer {
constructor() {
this.mockedClassInstance = new MockedClass('yo');
this.mockedClassInstance.mockedMethod('bro');
}
}
mocked-class-consumer.test.js - the test:
import MockedClassConsumer from './mocked-class-consumer';
import MockedClass from './mocked-class';
jest.mock('./mocked-class'); // Mocks the function that creates the class; replaces it with a function that returns undefined.
// console.log(MockedClass()); // logs 'undefined'
let mockedClassConsumer;
const mockedMethodImpl = jest.fn();
beforeAll(() => {
MockedClass.mockImplementation(() => {
// Replace the class-creation method with this mock version.
return {
mockedMethod: mockedMethodImpl // Populate the method with a reference to a mock created with jest.fn().
};
});
});
beforeEach(() => {
MockedClass.mockClear();
mockedMethodImpl.mockClear();
});
it('The MockedClassConsumer instance can be created', () => {
const mockedClassConsumer = new MockedClassConsumer();
// console.log(MockedClass()); // logs a jest-created object with a mockedMethod: property, because the mockImplementation has been set now.
expect(mockedClassConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
expect(MockedClass).not.toHaveBeenCalled(); // Ensure our mockClear() is clearing out previous calls to the constructor
const mockedClassConsumer = new MockedClassConsumer();
expect(MockedClass).toHaveBeenCalled(); // Constructor has been called
expect(MockedClass.mock.calls[0][0]).toEqual('yo'); // ... with the string 'yo'
});
it('We can check if the consumer called a method on the class instance', () => {
const mockedClassConsumer = new MockedClassConsumer();
expect(mockedMethodImpl).toHaveBeenCalledWith('bro');
// Checking for method call using the stored reference to the mock function
// It would be nice if there were a way to do this directly from MockedClass.mock
});
For me this kind of Replacing Real Class with mocked one worked.
// Content of real.test.ts
jest.mock("../RealClass", () => {
const mockedModule = jest.requireActual(
"../test/__mocks__/RealClass"
);
return {
...mockedModule,
};
});
var codeTest = require("../real");
it("test-real", async () => {
let result = await codeTest.handler();
expect(result).toMatch(/mocked.thing/);
});
// Content of real.ts
import {RealClass} from "../RealClass";
export const handler = {
let rc = new RealClass({doing:'something'});
return rc.realMethod("myWord");
}
// Content of ../RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "The.real.deal "+input;
}
// Content of ../test/__mocks__/RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "mocked.thing "+input;
}
Sorry if I misspelled something, but I'm writing it on the fly.

Sinon.js - stub React component's function before component is instantiated?

Let's say I have a component that looks like this:
var React = require('react/addons');
var ExampleComponent = React.createClass({
test : function () {
return true;
},
render : function () {
var test = this.test();
return (
<div className="test-component">
Test component - {test}
</div>
);
}
});
module.exports = ExampleComponent;
In my test, I could render this component using TestUtils, then stub out the method like so:
var renderedComponent = TestUtils.renderIntoDocument(<ExampleComponent/>);
sinon.stub(renderedComponent, 'test').returns(false);
expect(renderedComponent.test).toBe(false); //passes
But is there a way I could tell Sinon to automatically stub out a component's function every time an instance of that component is created? Ex:
sinon.stubAll(ExampleComponent, 'test').returns(false); //something like this
var renderedComponent = TestUtils.renderIntoDocument(<ExampleComponent/>);
expect(renderedComponent.test).toBe(false); //I'd like this to pass
If this isn't possible, is there a potential solution that comes close to providing the functionality I'm looking for?
You will need to overwrite the ExampleComponent.prototype instead of ExampleComponent. ExampleComponent is a constructor. Local methods like test() are saved in it's prototype.
sinon.stub(ExampleComponent.prototype, 'test').returns(false);
var renderedComponent = TestUtils.renderIntoDocument(<ExampleComponent/>);
expect(renderedComponent.test).toBe(false); //passes
I found a solution to my problem.
To clarify, my problem is that I wanted to stub out functions that belong to children components that are rendered under a parent component. So something like this:
parent.js
var Child = require('./child.js');
var Parent = React.createClass({
render : function () {
return (
<div className="parent">
<Child/>
</div>
);
}
});
module.exports = Parent;
child.js
var Child = React.createClass({
test : function () {
return true;
},
render : function () {
if (this.test) {
throw('boom');
}
return (
<div className="child">
Child
</div>
);
}
});
module.exports = Child;
If I were to use TestUtils to render Parent in one of my tests, it would throw the error, which I wanted to avoid. So my problem was that I needed to stub out Child's test function before it was instantiated. Then, when I render Parent, Child won't blow up.
The answer provided did not quite work, as Parent uses require() to get Child's constructor. I'm not sure why, but because of that, I can't stub out Child's prototype in my test and expect the test to pass, like so:
var React = require('react/addons'),
TestUtils = React.addons.TestUtils,
Parent = require('./parent.js'),
Child = require('./child.js'),
sinon = require('sinon');
describe('Parent', function () {
it('does not blow up when rendering', function () {
sinon.stub(Child.prototype, 'test').returns(false);
var parentInstance = TestUtils.renderIntoDocument(<Parent/>); //blows up
expect(parentInstance).toBeTruthy();
});
});
I was able to find a solution that fit my needs though. I switched my testing framework from Mocha to Jasmine, and I started using jasmine-react, which provided several benefits, including the ability to stub out a function of a class before it is instantiated. Here is an example of a working solution:
var React = require('react/addons'),
Parent = require('./parent.js'),
Child = require('./child.js'),
jasmineReact = require('jasmine-react-helpers');
describe('Parent', function () {
it('does not blow up when rendering', function () {
jasmineReact.spyOnClass(Child, 'test').and.returnValue(false);
var parentInstance = jasmineReact.render(<Parent/>, document.body); //does not blow up
expect(parentInstance).toBeTruthy(); //passes
});
});
I hope this helps someone else with a similar issue. If anyone has any questions I would be glad to help.

ReactJS and Jest - "object-assign" - Cannot call method 'addChangeListener' of undefined

So I'm testing a component where I need it to act with my store to get the correct testable behavior. I've read that, since the "View" requires the store to function, I need to not mock the store, and also to not mock the "object-assign" thingy.
Excerpts from test code:
jest.dontMock('object-assign');
jest.dontMock('../StationSearch.jsx');
jest.dontMock('../../stores/StationStore.js');
describe('StationSearch', function() {
var StationSearch;
var stationSearch;
var React;
var TestUtils;
beforeEach(function() {
React = require('react/addons');
TestUtils = React.addons.TestUtils;
StationSearch = require('../StationSearch.jsx');
stationSearch = TestUtils.renderIntoDocument(<StationSearch />);
});
it('is rendered without value by default', function() {
// code here
}
);
And some code from the View:
var React = require('react');
var StationStore = require('../stores/StationStore');
var StationSearch = React.createClass({
componentDidMount: function() {
StationStore.addChangeListener(this._onChange);
},
});
Now, when running my tests, I get
TypeError: Cannot call method 'addChangeListener' of undefined
...on my change listener line, despite that I (think that I) am doing the right things when it comes to "not mocking" things.
Anyone has a clue as to why I could be getting this, still?
probably late but...
the store is undefined because object-assign is being mocked. unmock it with one of the following methods
1) add this line to the top of your jest test file
jest.dontMock('object-assign');
2) add this to your package.json file
"jest": {
"unmockedModulePathPatterns": [
"object-assign"
]
},

Jasmine testing that a subclass object has been called through a Knockout subscribe

I've got a few things interacting here, and they aren't interacting well.
I have a base class:
var ObjOne = (function() {
return function() {
var self = this;
self.propertyOne = ko.observable(1);
self.observable = ko.observable(1);
self.observable.subscribe(function(newValue) {
self.propertyOne(newValue);
});
};
} ());
It has two Knockout observables, and defines a subscribe on one of them that updates the other.
I have a "subclass", extended with jQuery.extend:
var ObjTwo = (function() {
return function() {
this.base = new ObjOne();
$.extend(this, this.base);
};
} ());
And I have a Jasmine test, which is attempting to ask the question "when I update observable, is propertyOne called?"
it('Test fails to call the correct propertyOne', function() {
var obj = new ObjTwo();
spyOn(obj, 'propertyOne').andCallThrough();
obj.observable(2);
expect(obj.propertyOne).toHaveBeenCalled();
expect(obj.propertyOne()).toBe(2);
});
This fails with "Expected spy propertyOne to have been called.". When I debug, the observable is updated properly. In the actual system, it works fine (as well, even the test "is propertyOne equal to 2?" passes. When I debug into the subscribe function, self.propertyOne is not a spy, but in the test, it is.
I have a solution, but it isn't great:
it('Test calls the base propertyOne', function() {
var obj = new ObjTwo();
spyOn(obj.base, 'propertyOne').andCallThrough();
obj.observable(2);
expect(obj.base.propertyOne).toHaveBeenCalled();
expect(obj.propertyOne()).toBe(2);
});
Note the .base added to the two lines. I don't like that I've had to expose the base class, or had to touch it's properties in order to make the test run.
Here's a jsfiddle: http://jsfiddle.net/4DrrW/23/. The question is - is there a better way of doing this?
After you call $.extend(this, this.base); your object basically looks like:
{
base: {
propertyOne: ko.observable(1),
observable: ko.observable(1)
},
propertyOne: base.propertyOne,
observable: base.observable
}
When you do a spyOn for propertyOne it replaces it with a wrapper. However, the subscription is set between the actual observables and would not have any way to call the wrapper.
If you do not want to access base, then I would just remove the test that the observable was called. Checking that the value is correct seems sufficient.
Otherwise, you would probably be better off mixing in ObjOne by calling its constructor with the new object's this like:
var ObjTwo = (function() {
return function() {
ObjOne.call(this);
};
} ());
Then, the test would be fine: http://jsfiddle.net/rniemeyer/z2GU3/

Categories