I'm writing a unit test for my simple Node.js application using Mocha. The application has a class which connects to a Mongo database, fetch the record, and store the formulated record as a field. Simply, the class looks like this:
SampleClass.prototype.record = []; // Store the loaded record
SampleClass.prototype.init = function(db){
var self = this;
self.db = mongoose.connection; // Say we already have mongoose object initialized
self.db.once('open',function(){
/* schema & model definitions go here */
var DataModel = mongoose.model( /* foobar */);
DataModel.findOne(function(err,record){
/* error handling goes here */
self.record = record; // Here we fetch & store the data
});
});
}
As seen from the snippet above, once the SampleClass.init() is called, the Sample.record will not instantly get populated from the database. The data is asynchronously populated once the event 'open' is fired. Thus, there will possibly be a delay after SampleClass.init() until the Sample.record is populated.
So it comes into a complication when I write a Mocha test like this:
var testSampleClass = new SampleClass();
describe('SampleClass init test',function(){
testSampleClass.init('mydb');
it('should have 1 record read from mydb',function(){
assert.equal(testSampleClass.record.length,1);
});
});
The assertion above will always fail because testSampleClass.record will not get populated straightaway after init. It needs a gap of time to load the data.
How can I delay the test case so it starts a few seconds or more after testSampleClass.init is called? Is it also possible to trigger the test case right after an event of my class is fired? Otherwise, this simple case will always fail which I know this is not correct at all.
#alexpods made a great suggestion. Add following to your test collection so that each test step will wait for 500 msec before running.
beforeEach(function (done) {
setTimeout(function(){
done();
}, 500);
});
or in ES6
beforeEach(done => setTimeout(done, 500));
Thanks #Timmerz for the suggestion
Use before() or beforeEach hooks (see here and here). They take done callback as argument, which you must call when some asynchronous staff will be completed. So you test should looks like:
describe('SampleClass init test',function(){
before(function(done) {
testSampleClass.init('mydb', done);
});
it('should have 1 record read from mydb',function(){
assert.equal(testSampleClass.record.length,1);
});
});
And your init method:
SampleClass.prototype.record = []; // Store the loaded record
SampleClass.prototype.init = function(db, callback){
var self = this;
self.db = mongoose.connection; // Say we already have mongoose object initialized
self.db.once('open',function(){
/* schema & model definitions go here */
var DataModel = mongoose.model( /* foobar */);
DataModel.findOne(function(err,record){
/* error handling goes here */
self.record = record; // Here we fetch & store the data
callback();
});
});
}
Related
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.
I have an app which needs to check if the user has multiple accounts when it is first loaded. Most of them don't, so they should be taken straight into the main app view.
If they do have multiple accounts, they should be taken to a screen which allows them to select with which account they want to continue. The users with only one account should never see this selection page.
I have an OData call which returns the result of a backend check to see if the user has multiple accounts.
How can I dynamically set the rootView property of the component's metadata so it acts according to the behaviour defined above?
My research suggests if the rootView property is not set in the component metadata, the root view must be defined in the createContent() method.
My problem seems to stem around the asynchronous call to the OData, with createContent() executing before the OData result functions have set the variable to decide which view to see.
EDIT: I've changed the title to better reflect the likely cause of my problem. How can I keep the asynchronous calls but prevent createContent() from eventing before those calls are completed? Adding in something like the EventBus still doesn't work as it seems the application won't wait for it, and I just get an error about not being able to find the view.
My code below:
Declaration at the top of the file. By default view is the App since most users don't have multiple accounts. The multiple accounts is a rarer exception.
var sRootView = "view.App"; //default value of root view
sap.ui.core.UIComponent.extend("MY_APP.Component", {
Component.js
init: function(){
...
this.oModel = new sap.ui.model.odata.ODataModel("/odatasvc");
that = this;
var aBatch = [];
aBatch.push(this.oModel.createBatchOperation("/multiple_account_check", "GET"));
makeCall = function(){
var oDef = $.Deferred();
utils.oDataCalls.read(that.oModel, aBatch, oDef);
return oDef.promise();
};
var promise = makeCall();
var aResults = [];
promise.done(function(data) {
for (var i=0; i<data.length; i++) {
aResults.push(data);
}
that.checkAccounts(aResults[0]);
});
The createContent function.
createContent : function(){
var oView = sap.ui.view({
id : "meViewId",
viewName : sRootView,
type : "XML" });
return oView;
},
The function to check OData result for multiple accounts.
checkAccounts : function(aResults){
if(aResults.length == 1){ //one account
that.oRouter.navTo("home");
}
else if(aResults.length > 1){ //multiple accounts
sRootView = "view.multipleAccountsView"; //changed value used in createContent
that.oRouter.navTo("multipleAccounts");
}
}
You should be able to make the OData call synchronously by passing async as false to ODataModel method read. E.g:
oModel.read{"path", {async: false}};
If you implement this inside utils.oDataCalls.read, it might also require reworking the promise in that function.
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);
});
});
There must be something simple I am missing, but alas, I do not know what I do not know. Below is the code I have thus far for trying to get current streamflow conditions from the USGS.
// create site object
function Site(siteCode) {
this.timeSeriesList = [];
this.siteCode = siteCode;
this.downloadData = downloadData;
this.getCfs = getCfs;
// create reference to the local object for use inside the jquery ajax function below
var self = this;
// create timeSeries object
function TimeSeries(siteCode, variableCode) {
this.variableCode = variableCode;
this.observations = [];
}
// create observation object
function TimeSeriesObservation(stage, timeDate) {
this.stage = stage;
this.timeDate = timeDate;
}
// include the capability to download data automatically
function downloadData() {
// construct the url to get data
// TODO: include the capability to change the date range, currently one week (P1W)
var url = "http://waterservices.usgs.gov/nwis/iv/?format=json&sites=" + this.siteCode + "&period=P1W¶meterCd=00060,00065"
// use jquery getJSON to download the data
$.getJSON(url, function (data) {
// timeSeries is a two item list, one for cfs and the other for feet
// iterate these and create an object for each
$(data.value.timeSeries).each(function () {
// create a timeSeries object
var thisTimeSeries = new TimeSeries(
self.siteCode,
// get the variable code, 65 for ft and 60 for cfs
this.variable.variableCode[0].value
);
// for every observation of the type at this site
$(this.values[0].value).each(function () {
// add the observation to the list
thisTimeSeries.observations.push(new TimeSeriesObservation(
// observation stage or level
this.value,
// observation time
this.dateTime
));
});
// add the timeSeries instance to the object list
self.timeSeriesList.push(thisTimeSeries);
});
});
}
// return serialized array of cfs stage values
function getCfs() {
// iterate timeseries objects
$(self.timeSeriesList).each(function () {
// if the variable code is 00060 - cfs
if (this.variableCode === '00060') {
// return serialized array of stages
return JSON.stringify(this.observations);
}
});
}
}
When I simply access the object directly using the command line, I can access individual observations using:
> var watauga = new Site('03479000')
> watauga.downloadData()
> watauga.timeSeriesList[0].observations[0]
I can even access all the reported values with the timestamps using:
> JSON.stringify(watauga.timeSeriesList[0].observations)
Now I am trying to wrap this logic into the getCfs function, with little success. What am I missing?
I don't see anything in the code above that enforces the data being downloaded. Maybe in whatever execution path you're using to call getCfs() you have a wait or a loop that checks for the download to complete prior to calling getCfs(), but if you're simply calling
site.downloadData();
site.getCfs()
you're almost certainly not finished loading when you call site.getCfs().
You'd need to do invoke a callback from within your success handler to notify the caller that the data is downloaded. For example, change the signature of Site.downloadData to
function downloadData(downloadCallback) {
// ...
Add a call to the downloadCallback after you're finished processing the data:
// After the `each` that populates 'thisTimeSeries', but before you exit
// the 'success' handler
if (typeof downloadCallback === 'function') {
downloadCallback();
}
And then your invocation would be something like:
var watauga = new Site('03479000');
var downloadCallback = function() {
watauga.timeSeriesList[0].observations[0];
};
watauga.downloadData(downloadCallback);
That way, you're guaranteed that the data is finished processing before you attempt to access it.
If you're getting an undefined in some other part of your code, of course, then there may be something else wrong. Throw a debugger on it and step through the execution. Just bear in mind that interactive debugging has many of the same problems as interactively calling the script; the script has time to complete its download in the background before you start inspecting the variables, which makes it look like everything's hunky dory, when in fact a non-interactive execution would have different timing.
The real issue, I discovered through just starting over from scratch on this function, is something wrong with my implementation of jQuery.().each(). My second stab at the issue, I successfully used a standard for in loop. Here is the working code.
function getCfs() {
for (var index in this.timeSeriesList) {
if (this.timeSeriesList[index].variableCode === '00060'){
return JSON.stringify(this.timeSeriesList[index].observations);
}
}
}
Also, some of the stuff you are talking about #Palpatim, I definitely will have to look into. Thank you for pointing out these considerations. This looks like a good time to further investigate these promises things.
I've got a set of modules that run based on a global event emitter. They run based on a chronological chain of events, like so:
boot.ready
server created (because of boot.ready event)
server configured (because of server.created event)
As such, I need to create a server-test.js that performs tests in a chronological order.
Is this possible with Mocha? Something like the following?
var EventEmitter2 = require('eventemitter2').EventEmitter2,
should = require('should');
describe('server', function() {
var mediator = new EventEmitter2({
wildcard: false
});
require('../../src/routines/server/creator')(mediator);
require('../../src/routines/server/configurer')(mediator);
it('should be created after boot', function(done) {
mediator.once('server.created', function(server) {
server.should.exist;
done();
});
it('should be configured after created', function(done) {
mediator.once('server.configured', function() {
done();
});
});
mediator.emit('boot.ready');
});
});
Because there seemed to be some confusion about the way this global event emitter works, this is the server/creator.js module:
module.exports = function(mediator) {
var express = require('express');
mediator.once('boot.ready', function() {
var server = express.createServer();
//event: server created
mediator.emit('server.created', server);
});
};
As you can see, the server is created after boot.ready. This fires server.created, after which the configurer will run which will then fire server.configured.
This chain of events needs to be tested by mocha.
If I'm testing a chain of events the quick way is to do it is nested eventEmitter.once calls like this:
it('executes in the right sequence', function(done) {
mediator.once('boot.ready', function() {
mediator.once('server.created', function() {
done()
})
})
})
edit: as pointed out server.created will be fired before the test's boot.ready handler is fired. Here's a workaround:
it('executes in the right sequence', function(done) {
var bootReadyFired = false
mediator.once('boot.ready', function() {
bootReadyFired = true
})
mediator.once('server.created', function() {
assert.ok(bootReadyFired)
done()
})
})
Hope this helps.
Actually mocha use function.length to your it callbacks to know if you want them asynchronously, so with function(done) you can't known in which order they're run. function() without done argument will run them synchronously.
EDIT
Your mediator is an EventEmitter2 meaning that when you emit something, the handler will be run async. Like I said they's no way to known the order in which the are executed.
The problem is in you required modules, each event should probably be emitted in the handler of the previous. Code is better than words :
// require('../../src/routines/server/creator')(mediator);
// I guess this module creates the server, then it should emit server.created
// require('../../src/routines/server/configurer')(mediator)
// This one should listen to server.created then configure the server
// and finally emit server.configured
// So in it you should have something like this:
mediator.once('server.created', function(server) {
// Here you should configure you're server
// Once it is configured then you emit server.configured
});
Also you should know that emit is immediate in node so you'd better add your listeners before emitting.
Hope this is clear.