I'm trying to test a react component.
var Component = React.createClass({
componentDidMount: function () {
return this.setState({
name: 'blabla'
});
},
render: function () {
return (
<h1>{this.state.name}</h1>
);
}
});
Is there a way, during testing, to mock what componentDidMount returns or does? That would leave me to test it on it's own and just test the component render behaviour.
Thanks!
I prefer the following approach, but requires using ES6 classes.
// component.jsx
class Component extends React.Component {
componentDidMount() { return this.setState({name: 'blabla'}); }
render() { return (<h1>{this.state.name}</h1>); }
}
//component-spec.jsx
describe('Component', () => {
it('does stuff', () => {
let ComponentTest = class extends Component {
componentDidMount() {
// your override here
}
};
let component = TestUtils.renderIntoDocument(<ComponentTest />);
//expect(component...).toEqual(...)
});
});
The point is to create an on demand ChildClass inheriting the OriginalClass,
do whatever overrides and then TestUtils.renderIntoDocument(<ChildClass />)
The idea here, if I understand correctly, is that you're trying to stub out a function before a component is rendered in your test. In your case, componentWillMount is only called once in a component's lifecycle, immediately before the component is rendered. So you can't just render the component and then stub out the function, it must be done before the render occurs.
Let's take these components for example:
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;
Here, we would want to stub out the test function before our Child component is rendered, otherwise, it will blow up.
I have been able to do this using jasmine-react. These helper functions provide some useful functionality when running tests, almost to the point where TestUtils can be ditched completely.
jasmineReact.render(component, [container]) will render an instance of component into the DOM node specified in [container]. This is like TestUtils.renderIntoDocument(), except it renders the component into an attached DOM node instead of a detached DOM node. It will also perform the necessary cleaning operations when the test is finished.
jasmineReact.spyOnClass(componentClass, functionName) will stub out a particular function belonging to a component class. This behavior is maintained until the end of the test, which means that you can call this function before a component is rendered. This, if I understand correctly, is what you're looking for.
So, using these two helper functions, I can write a test for the code shown above that looks something like this:
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
});
});
Let me know if you have any questions.
I've found two ways to go about this (i'm sure there are more).
1) I've used sinon-chai and required in the base element class and then use rewireify to put a set a spy on the componentWillMount method. This works but not sure what test suites you're using.
2) Probably the easier way. Is to just use the TestUtils to get an instance of the component and then just manually run the componentWillMount method.
That second way would probably look something like (forgive the pesudo code):
it('should call state when it first mounts', function () {
var Component = require('../my-component');
var component = TestUtils.renderIntoDocument(<Component />);
component.setState({name: null});
component.componentWillMount();
expect(component.state.name).to.equal('blabla');
});
Related
I am trying to test whether componentWillMount was called and for that my test is
test('calls `componentWillMount` before rendering', () => {
let fn = jest.fn(SomeComponent.prototype.componentWillMount)
mount(<SomeComponent />)
expect(fn).toHaveBeenCalled()
})
But even though the componentWillMount method is called, the test does not pass.
What am I missing here?
I don't know if the other answers have helped with your question, but you shouldn't need to test componentWillMount. React should already do that testing for you.
More relevant to your testing would be to test the functions or actions you are putting in that method for your component.
If you are making some API call, running a function based on props, or anything else, that is what you should be testing for. Mock the function/action/code that componentWillMount triggers, and make assertions and expectations on that.
Example:
Component:
class YourComponent extends Component {
componentWillMount() {
/*this fetch function is actually what you want to test*/
this.props.fetch('data')
}
render() {
/* whatever your component renders*/
}
}
Tests:
test('should call fetch when mounted', () => {
let mockFetch = jest.fn()
const wrapper = mount(<SomeComponent fetch={mockFetch}/>);
expect(wrapper).toBeDefined();
expect(mockFetch).toHaveBeenCalled();
expect(mockFetch.mock.calls[0]).toEqual(['data'])
});
Try this:
test('calls `componentWillMount` before rendering', () => {
const onWillMount = jest.fn();
SomeComponent.prototype.componentWillMount = onWillMount;
mount(<SomeComponent />);
expect(onWillMount).toBeCalled();
});
I would first spy on the component's componentWillMount method but also use .and.CallThrough() to prevent it from mocking its contents. Hope this helps:
it('should check that the componentWillMount method is getting called', () => {
spyOn(SomeComponent.prototype, 'componentWillMount').and.callThrough();
const wrapper = mount(<SomeComponent />);
expect(wrapper).toBeDefined();
expect(SomeComponent.prototype.componentWillMount).toHaveBeenCalledTimes(1);
});
I don't believe the above answer addresses the issue. Which is jest allow you to spyOn a method but does not allow you to callThrough while spying on its call status. The solution that worked best for me is to setup the test with a component that has componentWillMount defined. Leaning on jest will just make things more complicated.
describe('componentWillMount', () => {
const calls = []
class Component1 extends Components {
componentWillMount() {
calls.push(new Date)
}
render() { ... }
}
afterEach(() => calls.splice(0, calls.length))
it('has been called', () => {
mount(<Component1 />)
expect(calls.length).toBe(1)
})
})
Use wrapper.instance().componentWillMount(); to call componentWillMount() method from test script..
I'm facing a problem trying to mock some of the Durandal dependencies... For example, I have this in my viewmodel (on the activate function):
routerFirstActiveFragment = router.activeInstruction().fragment.toLowerCase().split('/')[0];
I want to mock the router plugin, so, in my test file I have this:
define(['viewmodels/testvm', 'plugins/router'], function (testvm, router) {
describe('Module test', function () {
it('a test', function () {
spyOn(router, 'activeInstruction').andReturn('/get/33');
testvm.activate();
});
});
});
The thing is that when I do that, I receive this message when I run the test
TypeError: Cannot read property 'toLowerCase' of undefined
So, what I'm doing wrong?
Thanks in advance!
Well, it works like this:
spyOn(router, 'activeInstruction').andCallFake(function () {
return {
fragment: "/get/33"
};
});
I'm trying to test a controller that receives an object trough router's resolve:
app.js:
...
.when('/confirm', {
templateUrl: 'views/confirm.html',
controller: 'ConfirmCtrl',
resolve: {
order: function(Order) {
return Order.current;
}
}
})
...
ConfirmCtrl.js:
angular.module('angularGeolocationApp').controller('ConfirmCtrl',
function($scope, order, ...) {
$scope.order = order
...
});
My Test looks like this:
'use strict';
describe('Controller: ConfirmCtrl', function () {
// load the controller's module
beforeEach(module('angularGeolocationApp'));
var ConfirmCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
ConfirmCtrl = $controller('ConfirmCtrl', {
$scope: scope
});
}));
it('should get an order object', function () {
expect(_.isObject(scope.order)).toBeTruthy();
});
...
});
However first expectation fails:
PhantomJS 1.9.2 (Mac OS X) Controller: ConfirmCtrl should get an order object FAILED
TypeError: Attempted to assign to readonly property.
at workFn (/Users/jviotti/Projects/Temporal/angular/angular-geolocation/app/bower_components/angular-mocks/angular-mocks.js:2107)
Expected false to be truthy.
My assumption is that as I'm unit testing the isolated controller, the router doesn't have a change to run the resolve function and assign the correct value.
Is there a way to mock that order dependency?
I know I can get rid of the resolve stuff and inject Order and instantiate $scope.order to Order.current in the controller itself, but I'd like to keep the resolve approach.
Just put your own order into the constructor of the ctrl like this.
describe('Controller: ConfirmCtrl', function () {
// load the controller's module
beforeEach(module('angularGeolocationApp'));
var ConfirmCtrl,
scope,
order
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
order = {};
scope = $rootScope.$new();
ConfirmCtrl = $controller('ConfirmCtrl', {
$scope: scope,
order: order
});
}));
it('should get an order object', function () {
expect(_.isObject(scope.order)).toBeTruthy();
});
...
});
regards
I'm new to unit testing as well as the ng-animate module. I made a simple directive to test out ng-animate.
.directive('slideShow', function ($animate, $compile) {
return {
template: '<div class="slide-show-container"></div>',
restrict: 'EA',
replace: true,
link: function (scope, element, attrs) {
var newElement = $compile('<div class="slide-show-slide"></div>')(scope);
element.bind('mouseenter',function() {
element.append(newElement);
$animate.addClass(newElement, 'slide-enter');
});
element.bind('mouseleave',function() {
$animate.removeClass(newElement, 'slide-enter');
});
}
};
});
Then I made the following unit test to confirm that the .slide-enter class was being added.
it('should add slide-enter class', function () {
element.triggerHandler( "mouseenter" );
expect(element.children().hasClass("slide-enter")).toEqual(true)
});
The directive correctly added the class when I moused over it in a manual test. However the unit test failed and showed that the slide-enter class wasn't being added.
Finally I figured out the only way I could fix it was wrapping the unit test in a $timeout:
it('should add slide-enter class', inject(function ($timeout) {
element.triggerHandler( "mouseenter" );
$timeout(function() {
expect(element.children().hasClass("slide-enter")).toEqual(true);
});
$timeout.flush();
}));
Can anyone help me understand why this $timeout is required for the test to work? Is there another way to get this unit test to work that I'm messing?
NOTE I am using angular-animate 1.2.0-rc.2 and have documented my findings with this version. The need for the $timeout.flush() call seems to be fixed when looking at the 1.2.0-rc.3 code but I have not tested it yet. https://github.com/angular/angular.js/blob/v1.2.0-rc.3/src/ngAnimate/animate.js
I had the same problem with one of my tests. I was able to get my test to work by just calling $timeout.flush() after I had called the code that was supposed to trigger the adding of the class and before I called the expect. Your test should work if you rewrite it like:
it('should add slide-enter class', inject(function ($timeout) {
element.triggerHandler( "mouseenter" );
$timeout.flush();
expect(element.children().hasClass("slide-enter")).toEqual(true);
}));
I had to dig into the ngAnimate code to figure it out and this is what I found.
If you take a look at the angular-animate.js file at the addClass function. You will see the following:
addClass : function(element, className, done) {
performAnimation('addClass', className, element, null, null, function() {
$delegate.addClass(element, className, done);
});
}
The closure that is the last parameter to performAnimation is what will finally add the class.
In performAnimation, that last parameter is named 'onComplete`. There is a section of code that deals with calling this closure when animations should be skipped:
//skip the animation if animations are disabled, a parent is already being animated
//or the element is not currently attached to the document body.
if ((parent.inheritedData(NG_ANIMATE_STATE) || disabledAnimation).running) {
//avoid calling done() since there is no need to remove any
//data or className values since this happens earlier than that
//and also use a timeout so that it won't be asynchronous
$timeout(onComplete || noop, 0, false);
return;
}
And there is the call to $timeout that is causing the problem. When running this code in an angular test, the call to $timeout simply queues up the closure. The test code then has to call $timeout.flush() in order to get that function to run.
Let's say I have this:
App.ControllerMixin = Ember.Mixin.create({
setupController : function (entry) {
...
}
});
App.BaseEditController = Ember.ObjectController.extend(App.ControllerMixin, {
startEditing: function () {
...
this.setupController(entry);
},
});
App.ServicesEditController = App.BaseEditController.extend(App.ServicesMixin, {
setupController : function (entry) {
}
});
How can I call ControllerMixin.setupController from ServicesEditController.setupController?
You can call methods from super classes with this._super(). It is generally a good idea to add this call to each method you are overriding.
App.ServicesEditController = App.BaseEditController.extend(App.ServicesMixin, {
setupController : function (entry) {
this._super(entry);
}
});
Extending on my advise to add this call each overridden method, this is an example of a Mixin for a View. If your Mixin overrides didInsertElement, you should always add a call to this._super(). This ensures that "all" didInsertElement implementations get called, if multiple Mixins are applied.
App.SomeViewMixin = Ember.Mixin.create({
didInsertElement : function(){
this._super();
// ... perform your logic
}
});