Test a React Component function with Jest - javascript

Original
First of all, I am following the Flux architecture.
I have an indicator that shows a number of seconds, ex: 30 seconds. Every one second it shows 1 second less, so 29, 28, 27 till 0. When arrives to 0, I clear the interval so it stops repeating. Moreover, I trigger an action. When this action gets dispatched, my store notifies me. So when this happens, I reset the interval to 30s and so on. Component looks like:
var Indicator = React.createClass({
mixins: [SetIntervalMixin],
getInitialState: function(){
return{
elapsed: this.props.rate
};
},
getDefaultProps: function() {
return {
rate: 30
};
},
propTypes: {
rate: React.PropTypes.number.isRequired
},
componentDidMount: function() {
MyStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
MyStore.removeChangeListener(this._onChange);
},
refresh: function(){
this.setState({elapsed: this.state.elapsed-1})
if(this.state.elapsed == 0){
this.clearInterval();
TriggerAnAction();
}
},
render: function() {
return (
<p>{this.state.elapsed}s</p>
);
},
/**
* Event handler for 'change' events coming from MyStore
*/
_onChange: function() {
this.setState({elapsed: this.props.rate}
this.setInterval(this.refresh, 1000);
}
});
module.exports = Indicator;
Component works as expected. Now, I want to test it with Jest. I know I can use renderIntoDocument, then I can setTimeout of 30s and check if my component.state.elapsed is equal to 0 (for example).
But, what I want to test here are different things. I want to test if refresh function is called . Moreover, I'd like to test that when my elapsed state is 0, it triggers my TriggerAnAction(). Ok, for the first thing I tried to do:
jest.dontMock('../Indicator');
describe('Indicator', function() {
it('waits 1 second foreach tick', function() {
var React = require('react/addons');
var Indicator = require('../Indicator.js');
var TestUtils = React.addons.TestUtils;
var Indicator = TestUtils.renderIntoDocument(
<Indicator />
);
expect(Indicator.refresh).toBeCalled();
});
});
But I receive the following error when writing npm test:
Throws: Error: toBeCalled() should be used on a mock function
I saw from ReactTestUtils a mockComponent function but given its explanation, I am not sure if it is what I need.
Ok, in this point, I am stuck. Can anybody give me some light on how to test that two things I mentioned above?
Update 1, based on Ian answer
That's the test I am trying to run (see comments in some lines):
jest.dontMock('../Indicator');
describe('Indicator', function() {
it('waits 1 second foreach tick', function() {
var React = require('react/addons');
var Indicator = require('../Indicator.js');
var TestUtils = React.addons.TestUtils;
var refresh = jest.genMockFunction();
Indicator.refresh = refresh;
var onChange = jest.genMockFunction();
Indicator._onChange = onChange;
onChange(); //Is that the way to call it?
expect(refresh).toBeCalled(); //Fails
expect(setInterval.mock.calls.length).toBe(1); //Fails
// I am trying to execute the 1 second timer till finishes (would be 60 seconds)
jest.runAllTimers();
expect(Indicator.state.elapsed).toBe(0); //Fails (I know is wrong but this is the idea)
expect(clearInterval.mock.calls.length).toBe(1); //Fails (should call this function when time elapsed is 0)
});
});
I am still misunderstanding something...

It looks like you're on the right track. Just to make sure everyone's on the same page for this answer, let's get some terminology out of the way.
Mock: A function with behavior controlled by the unit test. You usually swap out real functions on some object with a mock function to ensure that the mock function is correctly called. Jest provides mocks for every function on a module automatically unless you call jest.dontMock on that module's name.
Component Class: This is the thing returned by React.createClass. You use it to create component instances (it's more complicated than that, but this suffices for our purposes).
Component Instance: An actual rendered instance of a component class. This is what you'd get after calling TestUtils.renderIntoDocument or many of the other TestUtils functions.
In your updated example from your question, you're generating mocks and attaching them to the component class instead of an instance of the component. In addition, you only want to mock out functions that you want to monitor or otherwise change; for example, you mock _onChange, but you don't really want to, because you want it to behave normally—it's only refresh that you want to mock.
Here is a proposed set of tests I wrote for this component; comments are inline, so post a comment if you have any questions. The full, working source for this example and test suite is at https://github.com/BinaryMuse/so-jest-react-mock-example/tree/master; you should be able to clone it and run it with no problems. Note that I had to make some minor guesses and changes to the component as not all the referenced modules were in your original question.
/** #jsx React.DOM */
jest.dontMock('../indicator');
// any other modules `../indicator` uses that shouldn't
// be mocked should also be passed to `jest.dontMock`
var React, IndicatorComponent, Indicator, TestUtils;
describe('Indicator', function() {
beforeEach(function() {
React = require('react/addons');
TestUtils = React.addons.TestUtils;
// Notice this is the Indicator *class*...
IndicatorComponent = require('../indicator.js');
// ...and this is an Indicator *instance* (rendered into the DOM).
Indicator = TestUtils.renderIntoDocument(<IndicatorComponent />);
// Jest will mock the functions on this module automatically for us.
TriggerAnAction = require('../action');
});
it('waits 1 second foreach tick', function() {
// Replace the `refresh` method on our component instance
// with a mock that we can use to make sure it was called.
// The mock function will not actually do anything by default.
Indicator.refresh = jest.genMockFunction();
// Manually call the real `_onChange`, which is supposed to set some
// state and start the interval for `refresh` on a 1000ms interval.
Indicator._onChange();
expect(Indicator.state.elapsed).toBe(30);
expect(setInterval.mock.calls.length).toBe(1);
expect(setInterval.mock.calls[0][1]).toBe(1000);
// Now we make sure `refresh` hasn't been called yet.
expect(Indicator.refresh).not.toBeCalled();
// However, we do expect it to be called on the next interval tick.
jest.runOnlyPendingTimers();
expect(Indicator.refresh).toBeCalled();
});
it('decrements elapsed by one each time refresh is called', function() {
// We've already determined that `refresh` gets called correctly; now
// let's make sure it does the right thing.
Indicator._onChange();
expect(Indicator.state.elapsed).toBe(30);
Indicator.refresh();
expect(Indicator.state.elapsed).toBe(29);
Indicator.refresh();
expect(Indicator.state.elapsed).toBe(28);
});
it('calls TriggerAnAction when elapsed reaches zero', function() {
Indicator.setState({elapsed: 1});
Indicator.refresh();
// We can use `toBeCalled` here because Jest automatically mocks any
// modules you don't call `dontMock` on.
expect(TriggerAnAction).toBeCalled();
});
});

I think I understand what you're asking, at least part of it!
Starting with the error, the reason you are seeing that is because you have instructed jest to not mock the Indicator module so all the internals are as you have written them. If you want to test that particular function is called, I'd suggest you create a mock function and use that instead...
var React = require('react/addons');
var Indicator = require('../Indicator.js');
var TestUtils = React.addons.TestUtils;
var refresh = jest.genMockFunction();
Indicator.refresh = refresh; // this gives you a mock function to query
The next thing to note is you are actually re-assigning the Indicator variable in your example code so for proper behaviour I'd rename the second variable (like below)
var indicatorComp = TestUtils.renderIntoDocument(<Indicator />);
Finally, if you want to test something that changes over time, use the TestUtils features around timer manipulation (http://facebook.github.io/jest/docs/timer-mocks.html). In your case I think you can do:
jest.runAllTimers();
expect(refresh).toBeCalled();
Alternatively, and perhaps a little less fussy is to rely on the mock implementations of setTimeout and setInterval to reason about your component:
expect(setInterval.mock.calls.length).toBe(1);
expect(setInterval.mock.calls[0][1]).toBe(1000);
One other thing, for any of the above changes to work, I think you'll need to manually trigger the onChange method as your component will initially be working with a mocked version of your Store so no change events will occur. You'll also need to make sure that you've set jest to ignore the react modules otherwise they will be automatically mocked too.
Full proposed test
jest.dontMock('../Indicator');
describe('Indicator', function() {
it('waits 1 second for each tick', function() {
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
var Indicator = require('../Indicator.js');
var refresh = jest.genMockFunction();
Indicator.refresh = refresh;
// trigger the store change event somehow
expect(setInterval.mock.calls.length).toBe(1);
expect(setInterval.mock.calls[0][1]).toBe(1000);
});
});

Related

Starting Alexa Skill in a specific state

Earlier I ran into the issue of Alexa not changing the state back to the blank state, and found out that there is a bug in doing that. To avoid this issue altogether, I decided that I wanted to force my skill to always begin with START_MODE.
I used this as my reference, where they set the state of the skill by doing alexa.state = constants.states.START before alexa.execute() at Line 55. However, when I do the same in my code, it does not work.
Below is what my skill currently looks like:
exports.newSessionHandler = {
LaunchRequest () {
this.hander.state = states.START;
// Do something
}
};
exports.stateHandler = Alexa.CreateStateHandler(states.START, {
LaunchRequest () {
this.emit("LaunchRequest");
},
IntentA () {
// Do something
},
Unhandled () {
// Do something
}
});
I'm using Bespoken-tools to test this skill with Mocha, and when I directly feed IntentA like so:
alexa.intended("IntentA", {}, function (err, p) { /*...*/ })
The test complains, Error: No 'Unhandled' function defined for event: Unhandled. From what I gather, this can only mean that the skill, at launch, is in the blank state (because I have not defined any Unhandled for that state), which must mean that alexa.state isn't really a thing. But then that makes me wonder how they made it work in the example code above.
I guess a workaround to this would be to create an alias for every intent that I expect to have in the START_MODE, by doing:
IntentA () {
this.handler.state = states.START;
this.emitWithState("IntentA");
}
But I want to know if there is a way to force my skill to start in a specific state because that looks like a much, much better solution in my eyes.
The problem is that when you get a LaunchRequest, there is no state, as you discovered. If you look at the official Alexa examples, you will see that they solve this by doing what you said, making an 'alias' intent for all of their intents and just using them to change the state and then call themselves using 'emitWithState'.
This is likely the best way to handle it, as it gives you the most control over what state and intent is called.
Another option, assuming you want EVERY new session to start with the same state, is to leverage the 'NewSession' event. this event is triggered before a launch request, and all new sessions are funneled through it. your code will look somewhat like this:
NewSession () {
if(this.event.request.type === Events.LAUNCH_REQUEST) {
this.emit('LaunchRequest');
} else if (this.event.request.type === "IntentRequest") {
this.handler.state = states.START;
this.emitWithState(this.event.request.intent.name);
}
};
A full example of this can be seen here (check out the Handlers.js file): https://github.com/alexa/skill-sample-node-device-address-api/tree/master/src
I would also recommend reading through this section on the Alexa GitHub: https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs#making-skill-state-management-simpler
EDIT:
I took a second look at the reference you provided, and it looks like they are setting the state outside of an alexa handler. So, assuming you wanted to mimic what they are doing, you would not set the state in your Intent handler, but rather the Lambda handler itself (where you create the alexa object).
exports.handler = function (event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = appId;
alexa.registerHandlers(
handlers,
stateHandlers,
);
alexa.state = START_MODE;
alexa.execute();
};

Javascript / meteor android return objects to templates

I have the following piece of code in my meteor app
if (Meteor.isClient) {
Meteor.startup(function(){
var onSuccess = function(acceleration){
alert( acceleration);
};
var onError = function(acceleration){
return "error";
}
var options = { frequency: 3000 }; // Update every 3 seconds
var getAcc = navigator.accelerometer.watchAcceleration(onSuccess, onError, options);
});
}
What that does is retrieve the android phones accelerometer data every 3 seconds, and on a successful poll it will show the object in an alert. This works fine.
However, I don't want this polling code to be in the startup function. I want to have more control over when this is executed
I have a template where I want to display the accelerometer values. I changed the onSuccess method in the code above to return the object instead of alerting (the rest of the startup code is the same):
var onSuccess = function(acceleration){
return acceleration;
};
My template looks like this:
Template.rawData.helpers({
acc: function(){
alert(getAcc);
return getAcc;
}
});
What I'm expecting to happen is for the accelerometer data to be stored in getAcc in the startup function, but then to return it through acc to my webpage. This does not seem to happen. The alert in the template doesn't occur either
Is there a way to access these cordova plugins from outside of the startup function? Am I just incorrectly returning the objects between the startup and template sections?
I guess my other overarching question is this: I'm not sure how to display those accelerometer values through a template if theyre gathered in the startup function, and not from a template helper
You need to have the template update reactively when the data changes. To do that set up a reactive variable that gets updated by the callback. First, install the package:
meteor add reactive-var
Then, when the template is created, create the reactive variable and start watching the callbacks:
Template.rawData.onCreated(function() {
self = this;
self.rawValue = new ReactiveVar();
self.accWatchID = navigator.accelerometer.watchAcceleration(
function(acc) { self.rawValue.set(acc); }, function() {}, { frequency: 3000 }
);
});
Your template helper can then return the value of the reactive variable, and your template will be updated whenever it changes:
Template.rawData.helpers({
acc: function() {
return Template.instance().rawValue.get();
}
});
(Note that in your original code, since the alert wasn't being called, there must be a problem in your template. Does it have the right name?)
Finally, you should stop the callback when the template is destroyed:
Template.rawData.onDestroyed(function() {
navigator.accelerometer.clearWatch(this.accWatchID);
});
Note that I've just typed that code here without testing it. You may need to fine tune it a little if it doesn't work straight away.

React JS synchronous handling of array.map

I am very new to React JS and in some ways javascript here is the problem I am trying to solve. I have a function in a component that looks like the below that iterates through an array of numbers:
playerSequence: function() {
var game = this;
this.state.game.sequence.map(function(currNum){
console.log(currNum);
switch(currNum){
case 1:
game.handleBlue();
break;
case 2:
game.handleYellow();
break;
case 3:
game.handleGreen();
break;
case 4:
game.handleRed();
break;
}
})
},
Each function call has a setTimeout that waits a period of time and renders a light and then switches off the light after this.props.wait ms. Below is an example:
var RedOn = React.createClass({
mixins: [TimerMixin],
componentDidMount: function () {
this.setTimeout(function(){this.props.handleRed()}, this.props.wait);
},
handleRed: function() {
this.props.handleRed()
},
renderstuff
});
What I would like is for each pass through the map array to wait until the function call is finished and then continue. As it is right now they all go off at the same time. I am sure I am not fully understanding the nature of node, react or a combo of both. Any help would be appreciated.
According to the React docs, componentDidMount is Invoked once, only on the client (not on the server), immediately after the initial rendering occurs.
So it would make sense that they would all fire at once. The other problem is that setTimeout is invoked as soon as its called, which means if you pass some time in milliseconds to each function, map will simply invoke them all at once, not apply them sequentially. If you wanted to keep using map, you should declare a variable to store previous time applied, and add it into each timeout.
var previousTime = 0;
["foo", "bar", "baz"].map( function(e) {
setTimeout( function() { console.log(e); }, 1000 + previousTime);
previousTime += 1000; });
Here's a trivial example of what I'm describing. Try running this in a console to see the result. Every time you call "setTimeout" you add the time to the previousTime variable, so that each element waits an additional second before showing.
At the end of the day, even with all the abstractions offered by React, remember It'sJustJavaScriptâ„¢

How can I make Protractor NOT wait for $timeout?

I'm testing my angular application with Protractor.
Once the user is logged in to my app, I set a $timeout to do some job in one hour (so if the user was logged-in in 13:00, the $timeout will run at 14:00).
I keep getting these failures:
"Timed out waiting for Protractor to synchronize with the page after 20 seconds. Please see https://github.com/angular/protractor/blob/master/docs/faq.md. The following tasks were pending: - $timeout: function onTimeoutDone(){....."
I've read this timeouts page: https://github.com/angular/protractor/blob/master/docs/timeouts.md
so I understand Protractor waits till the page is fully loaded which means he's waiting for the $timeout to complete...
How can I make Protractor NOT wait for that $timeout?
I don't want to use:
browser.ignoreSynchronization = true;
Because then my tests will fail for other reasons (other angular components still needs the time to load...)
The solution will be to flush active timeouts (as #MBielski mentioned it in comments), but original flush method itself is available only in anuglar-mocks. To use angular-mocks directly you will have to include it on the page as a <script> tag and also you'll have to deal with all overrides it creates, it produces a lot of side effects. I was able to re-create flush without using angular-mocks by listening to any timeouts that get created and then reseting them on demand.
For example, if you have a timeout in your Angular app:
$timeout(function () {
alert('Hello World');
}, 10000); // say hello in 10 sec
The test will look like:
it('should reset timeouts', function () {
browser.addMockModule('e2eFlushTimeouts', function () {
angular
.module('e2eFlushTimeouts', [])
.run(function ($browser) {
// store all created timeouts
var timeouts = [];
// listen to all timeouts created by overriding
// a method responsible for that
var originalDefer = $browser.defer;
$browser.defer = function (fn, delay) {
// originally it returns timeout id
var timeoutId = originalDefer.apply($browser, arguments);
// store it to be able to remove it later
timeouts.push({ id: timeoutId, delay: delay });
// preserve original behavior
return timeoutId;
};
// compatibility with original method
$browser.defer.cancel = originalDefer.cancel;
// create a global method to flush timeouts greater than #delay
// call it using browser.executeScript()
window.e2eFlushTimeouts = function (delay) {
timeouts.forEach(function (timeout) {
if (timeout.delay >= delay) {
$browser.defer.cancel(timeout.id);
}
});
};
});
});
browser.get('example.com');
// do test stuff
browser.executeScript(function () {
// flush everything that has a delay more that 6 sec
window.e2eFlushTimeouts(6000);
});
expect(something).toBe(true);
});
It's kinda experimental, I am not sure if it will work for your case. This code can also be simplified by moving browser.addMockModule to a separate node.js module. Also there may be problems if you'd want to remove short timeouts (like 100ms), it can cancel currently running Angular processes, therefore the test will break.
The solution is to use interceptors and modify the http request which is getting timeout and set custom timeout to some milliseconds(your desired) to that http request so that after sometime long running http request will get closed(because of new timeout) and then you can test immediate response.
This is working well and promising.

Qunit test alternates between pass and fail on page refresh

I have a two tests that are causing side effects with each other. I understand why as I am replacing a jQuery built-in function that is being called internally in the second test. However what I don't understand is why the test alternately passes and fails.
This question is similar However, I am not doing anything directly on the qunit-fixture div.
Here are my tests
test('always passing test', function() { // Always passes
var panelId = '#PanelMyTab';
var event = {};
var ui = {
tab: {
name: 'MyTab',
},
panel: panelId,
};
$('<div id="' + panelId + '">')
.append('Test')
.append('Show Form')
.appendTo('#qunit-fixture');
jQuery.fn.on = function(event, callback) {
ok(this.selector == panelId + ' .export', 'Setting export click event');
equal(callback, tickets.search.getReport, 'Callback being set');
};
loadTab(event, ui);
});
test('alternately passing and failing', function() { // Alternates between passing and failing on page refresh
expect(5);
var testUrl = 'test';
$('<div class="ui-tabs-panel">')
.append('Get Report')
.append('<form action="notest" target="" class="ticketSearch"></form>')
.appendTo('#qunit-fixture');
// Setup form mocking
$('form.ticketSearch').submit(function() {
var urlPattern = new RegExp(testUrl + '$');
ok(urlPattern.test($(this).prop('action')), 'Form action set to link href');
equal($(this).prop('target'), '_blank', 'Open form on a new page');
});
var event = {
target: 'a#getReport',
};
var result = getReport(event);
var form = $('form.ticketSearch');
ok(/notest$/.test($(form).prop('action')), 'Making sure action is not replaced');
equal($(form).prop('target'), '', 'Making sure that target is not replaced');
ok(false === result, 'click event returns false to not refresh page');
});
The tests will start off passing but when I refresh they will alternate between passing and failing.
Why is this happening? Even adding GET parameters to the url result in the same behavior on the page.
In the failing cases, the test is failing because internal jQuery is calling .on() when the submit() handler is set. But why isn't the test always failing in that case? What is the browser doing that a state is being retained during page refresh?
Update:
Here is the code that is being tested:
var tickets = function() {
var self = {
loadTab: function(event, ui) {
$(panel).find('.export').button().on('click', this.getReport);
},
search: {
getReport: function(event) {
var button = event.target;
var form = $(button).closest('div.ui-tabs-panel').find('form.ticketSearch').clone(true);
$(form).prop('action', $(button).prop('href'));
$(form).prop('target', '_blank');
$(form).submit();
return false;
}
}
};
return self;
}();
I've modified #Ben's fiddle to include your code with both of your tests. I modified some of your code to make it run correctly. When you hit the run button all of the tests will pass. When you hit the run button again, the second test ("alternately passing and failing") will fail -- this is basically simulating your original issue.
The issue is your first test ("always passing test") alters the global state by replacing the jQuery.fn.on function with an overridden one. Because of this, when the tests are run in order, the second test ("alternately passing and failing") uses the incorrect overridden jQuery.fn.on function and fails. Each unit test should return the global state back to its pre-test state so that other tests can run based on the same assumptions.
The reason why it's alternating between pass and fail is that under the hood QUnit always runs failed tests first (it remembers this somehow via cookie or local storage, I'm not exactly sure). When it runs the failed tests first, the second test runs before the first one; as a result, the second test gets jQuery's native on function and works. When you run it a third time, the tests will run in their "original" order and the second test will use the overridden on function and fail.
Here's the working fiddle. I've add the fix to "un-override" the on function after the test by caching the original var jQueryOn = jQuery.fn.on; function and resetting it at the end of the test via: jQuery.fn.on = jQueryOn;. You can probably better implement this using QUnit's module teardown() method instead.
You can check out https://github.com/jquery/qunit/issues/74 for more info.
I'm not sure I can solve this without some more info, but I can point out some possible issues.
The first test seems to have invalid syntax on line 2
var panelId = '#PanelMyTab');
But that's probably a type mistake, seeing as you say the first always passes.
I'm assuming that for the first test to pass(and be valid) the loadTab(event,ui) must run the jQuery.fn.on(), without it no assertions have been run. Which doing some testing with jQuery UI Tabs, seems to be the case (just not sure if it was your intention).
I'm not sure it's advisable putting these assertions within that function, and you must understand that you have overwritten the jquery function with a function that doesn't do anything, so it's likely to cause issues.
You seem to be doing something similar in the second test, you are expecting 5 assertions, but I can only see how the final 3 can be run
ok(/notest$/.test($(form).prop('action')), 'Making sure action is not replaced');
equal($(form).prop('target'), '', 'Making sure that target is not replaced');
ok(false === result, 'click event returns false to not refresh page');
The other 2 are within a submit function that doesn't look like it is invoked as part of the test.
Remember these tests are synchronous so it won't wait for you to hit submit before running the test and failing.
Here is an example
test('asynchronous test', function() {
setTimeout(function() {
ok(true);
}, 100)
})
Would fail as the ok is run 100ms after the test.
test('asynchronous test', function() {
// Pause the test first
stop();
setTimeout(function() {
ok(true);
// After the assertion has been called,
// continue the test
start();
}, 100)
})
The stop() tells qunit to wait and the start() to go!
There is also a asyncTest() detailed in the api here
Finally, it seems like you are trying to debug your code with these tests. It would be much easier to use chrome developer tools or firebug in firefox to set breakpoints on your code, and use console.log() and console.dir() to output information.
That being said I have no idea how it works for you at all, so I could be missing something :) If you're still stuck, see if you can add some more of the surrounding code and what your trying to achieve. Hope this helps.
PS: there is also a }; at the end which is invalid in the code you have given us, probably relevant in the actual application though ;)

Categories

Resources