So I am using Reflux for my stores and actions. I have an application store that some application wide data including a flag on whether or not prevent double click is enabled. my tests look like this (using mocha, chai, and sinon):
var applicationStore = require('../../../../web/app/components/core/application.store.js');
var initialState = _.clone(applicationStore._internalData, true);
function resetToInitialState() {
applicationStore._internalData = _.clone(initialState, true);
}
chai.use(sinonChai);
describe('application store', function() {
beforeEach(function() {
resetToInitialState();
});
it('should have default data set properly', function() {
expect(applicationStore.getPreventDoubleClick()).to.be.false;
});
it('should be able to enable prevent double click flag', function() {
applicationStore._onEnablePreventDoubleClick();
expect(applicationStore.getPreventDoubleClick()).to.be.true;
});
it('should be able to disable prevent double click flag', function() {
applicationStore._onEnablePreventDoubleClick();
applicationStore._onDisablePreventDoubleClick();
expect(applicationStore.getPreventDoubleClick()).to.be.false;
});
});
Since the all stores are effectively singletons, is manually reseting it's internal data in the beforeEach a valid way to test to make sure no test effects another one? Is there a better way of doing this with mocha/chai/sinon?
Since I use JestJS I haven't ran into that issue. It seems to deal with singletons really well.
I'm assuming, recommending to switch testing frameworks is out of the question.
If that is the case, then your set up seems ok. My other thought would be to have it inline with the store.
var _internalData = false;
var applicationStore = {
reset: function() {
_internalData = false;
}
};
Then you could just call applicationStore.reset() to reset it, and test the reset functionality.
Neither ideal really.
Related
I've run into a weird problem writing integration tests for my component. When I run each one individually, they pass. When I run multiple, the first one passes and the other ones fail. I think it has something to do with closure actions but I don't know.
Here's my component code
// components/game-nav-key.js
triggerKeyAction(code) {
if (this.get('prevKeyCode').contains(code)) {
this.sendAction('onPrevKey', true);
} else if (this.get('nextKeyCode').contains(code)) {
this.sendAction('onNextKey', true);
} else if (this.get('openKeyCode').contains(code)) {
this.sendAction('onOpenKey');
}
},
didInsertElement() {
var self = this;
Ember.$('body').keydown(function(e) {
self.triggerKeyAction(e.which);
});
Ember.$('body').keyup(function(e) {
});
}
And my tests
// game-nav-key-test.js
it('tracks key commands and sends an action for K', function() {
let spy = sinon.spy();
this.set('gotoPrev', spy);
this.render(hbs`
{{game-nav-key onPrevKey=(action gotoPrev)}}
`);
triggerKeydown($('body'), 75);
triggerKeyup($('body'), 75);
sinon.assert.calledOnce(spy);
sinon.assert.calledWith(spy, true);
});
it('tracks key commands and sends an action for J', function() {
let spy = sinon.spy();
this.set('gotoNext', spy);
this.render(hbs`
{{game-nav-key onNextKey=(action gotoNext)}}
`);
triggerKeydown($('body'), 74);
triggerKeyup($('body'), 74);
sinon.assert.calledOnce(spy);
sinon.assert.calledWith(spy, true);
});
it('tracks key commands and sends an action for R', function() {
let spy = sinon.spy();
this.set('open', spy);
this.render(hbs`
{{game-nav-key onOpenKey=(action open)}}
`);
triggerKeydown($('body'), 82);
triggerKeyup($('body'), 82);
sinon.assert.calledOnce(spy);
});
I removed all beforeEach's, so it's literally just those three tests. Like I said, each one passes individually, and when it is listed first, but the second two fail when run together. Note that using console.log statements I have verified that the code hits the line directly above each of the this.sendAction calls in their respective tests
seems you need to destroy your listeners created in didInsertElement
willDestroyElement() {
Ember.$('body').off('keydown');
Ember.$('body').off('keyup');
}
I have an ember service thats primary concern is to fetch data for a specific model and the descendants of the model. The reason I am using this in a service is because the route for this particular type is using a slug which is not the primary key and therefore needs to do a store.query instead of store.find. When we fetch this model I have some logic that peeks the ember store to see if we can load it from there before going to the api query. Also this vendor is watching for the slug change and updating the current model based on that.
The problem I am having is that this seems to have very little documentation when it comes to how to test a thing like this. In fact I don't see a section on testing services anywhere in the guides here http://guides.emberjs.com/v2.1.0/
This is a snippet of the service in question.
import Ember from 'ember';
export default Ember.Service.extend({
_vendorSlug: null,
vendor: null,
vendorSlug: function (key, value) {
if (arguments.length > 1) {
if (this._vendorSlug) {
return this._vendorSlug;
}
this._vendorSlug = value;
}
return this._vendorSlug;
}.property(),
ensureVendorLoaded: function (slug) {
var service = this,
vendorSlug = slug || service.get('vendorSlug'),
currentVendor = service.get('vendor'),
storedVendor;
if (!Ember.isNone(currentVendor) && (vendorSlug === currentVendor.get('slug'))) {
return new Ember.RSVP.Promise((resolve) => {
resolve(currentVendor);
});
} else {
var storedVendors = service.store.peekAll('vendor').filter((vendor) => {
return vendor.get('slug') === vendorSlug;
});
if (storedVendors.length) {
storedVendor = storedVendors[0];
}
}
if (!Ember.isNone(storedVendor)) {
service.set('vendorSlug', storedVendor.get('slug'));
return new Ember.RSVP.Promise((resolve) => {
resolve(storedVendor);
});
}
return service.store.queryRecord('vendor', {slug: vendorSlug}).then((vendor) => {
service.set('vendor', vendor);
service.set('vendorSlug', vendor.get('slug'));
return vendor;
});
},
_vendorSlugChanged: function () {
if (this.get("vendorSlug") === this.get("vendor.slug")) {
return;
}
this.ensureVendorLoaded();
}.observes('vendorSlug')
});
I would like to be able to assert a couple of scenarios here with the store interaction. Vendor already set, vendor loaded from the peek filter, and vendor loaded from query.
I think I have finally come to a reasonable conclusion. Let me share with you what I think may be the best way to approach unit testing services that rely on the store.
The answer really lies in the assumption we must make when writing unit tests. That is, everything outside of our logical unit should be considered to work properly and our units should be completely independent.
Thus, with a service relying on the store it is best to create a stub or mock (see this question to understand the difference between a mock and a stub) for the store. A stub for the store itself is quite simple. Something like this will do:
store: {
find: function() {
var mockedModel = Ember.Object.create({/*empty*/});
return mockedModel;
},
query: ...
}
If you prefer to use a mock instead you could do something like the following (i made this really fast so it might not work completely but its enough to get the idea across):
import Ember from 'ember';
class MockStore {
constructor() {
this.models = Ember.A([]);
}
createRecord(modelName, record) {
// add a save method to the record
record.save = () => {
return new Ember.RSVP.Promise((resolve) => {
resolve(true);
});
};
if (!this.models[modelName]) {
this.models[modelName] = Ember.A([]);
}
this.models[modelName].pushObject(record);
return record;
}
query(modelName, query) {
let self = this;
return new Ember.RSVP.Promise((resolve) => {
let model = self.models[modelName];
// find the models that match the query
let results = model.filter((item) => {
let result = true;
for (let prop in query) {
if (query.hasOwnProperty(prop)) {
if (!item[prop]) {
result = false;
}
else if (query[prop] !== item[prop]) {
result = false;
}
}
}
return result;
});
resolve(results);
});
}
}
export default MockStore;
Next all you have to do is to set the store property (or whatever your calling it) on your service to a new mock store instance when you run a test. I did this like so:
import Ember from 'ember';
import { moduleFor, test } from 'ember-qunit';
import MockStore from '../../helpers/mock-store';
let session;
let store;
const username = '';
const password = '';
moduleFor('service:authentication', 'Unit | Service | authentication', {
beforeEach() {
session = Ember.Object.create({});
store = new MockStore();
}
});
test('it should authenticate the user', function (assert) {
// this sets the store property of the service to the mock store
let authService = this.subject({session: session, store: store});
authService.authenticate(username, password).then(() => {
assert.ok(session.get('username'));
});
});
The documentation on testing these situations is definitely poor, so perhaps there is a better method, but this is what I will be rolling with for now. Also, if you check out the Discourse project, which uses ember, they follow a similar pattern to what I described here, but in a little more advanced manner.
I'm not sure this is the answer you want, but I'll give it a shot anyway. An Ember Service is not really much more than an Ember Object and if you're "unit testing" that Service, it should be in isolation of its dependencies (otherwise it wouldn't be a unit test).
From my understanding (and this could be wrong). If you want to test that service you need to replace the store with a mock implementation.
//tests/unit/services/my-service.js
test('some scenario', function(assert) {
let service = this.subject({
store: Ember.Object.create({
peekAll(modelName){
//Return array for this scenario
},
query(model, params){
//Return array for this scenario
}
});
});
assert.ok(service);
});
I also think this is why there's little documentation testing services.
One resource I recommend about services is this talk from the Chicago Ember Meetup
I am trying to test a controller which uses angular's $resource.
function PermissionsCtrl($scope, $resource, $cookies) {
var Object = $resource('/v1/objects/:id');
loadObjects();
function loadObjects() {
$scope.myAppObjects = new Array();
var data = AppObject.get({find: '{"app_id": '+wtm.app_id+'}'},
function(){
if (data.results) {
for(var i = 0; i< data.results.length; i++) {
if(!data.results[i].is_deleted) {
(function(i){
$scope.objects(data.results[i]);
}(i));
}
}
}
},
function(error){console.log(error);});
}
And here is the test code.
var apiServer = "...";
var app_id = 999
var mock_object_data = {...};
describe('apps permissionsCtrl', function(){
var scope, ctrl, $httpBackend;
// Create a matcher for comparing data
beforeEach( function() {
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
// Create the controller with injected data/services
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller, $resource) {
$httpBackend = _$httpBackend_;
// cookie data to inject
var cookies = new Array();
cookies['id'] = '...'; // just needs to be declared
$httpBackend.expectGET(apiServer+'/v1/app_objects? find=%7B%22app_id%22:'+app_id+'+%7D&access_token=' + cookies['id'])
.respond( mock_object_data );
var $injector = angular.injector(['ng', 'ngResource']);
var $resource = $injector.get('$resource');
scope = $rootScope.$new();
ctrl = $controller(PermissionsCtrl, {$scope: scope, $cookies: cookies, $resource: $resource});
}));
it('should put object data into $scope', function() {
$httpBackend.flush();
expect(scope.objects).toEqualData( mock_object_data );
});
});
When I run this I get
Error: Unknown provider: $resourceProvider <- $resource
at the line where I try to create my controller. I don't understand how to inject this into my controller and no matter what I try I get the same error. A couple things I've tried are
Declaring a an empty mock object and passing it through similar to my cookies variable. I figure this is probably a bad solution anyway since I actually want to use the service.
Mimicking the scope mock and passing it into my inject function and passing $resource.$new() to my controller.
Doing nothing and hoping that httpBackend would cover it since that's what ultimately gets called anyway. Vojta Jína made it sound like that would work but no dice.
Mild epithets. Satisfying but not very effective.
After more reading and more experimenting it seems the right way to do this is to abstract the use of $resource out of the controller. In my case I wrote a service that relies on $resource and then inject that service into my controller. Meanwhile I test that service separately from my controller. Better practice all around.
My service declaration:
angular.module('apiModule', ['localResource', 'ngCookies'])
.factory('apiService', function($resource, $cookies) {
and in my unit tests I pass it through in a beforeEach setup function
beforeEach(module('apiModule'));
Try to instantiate $resource with the following code:
var $injector = angular.injector(['ng', 'ngResource']);
var $resource = $injector.get('$resource');
Similar for other services, except when they are in other modules still. Then add that module to the array.
More info
I have the following angular controller
function IndexCtrl($scope, $http, $cookies) {
//get list of resources
$http.get(wtm.apiServer + '/v1/developers/me?access_token=' + $cookies['wtmdevsid']).
success(function(data, status, headers, config) {
// snip
}).
error(function(data, status, headers, config) {
// snip
});
$scope.modal = function() {
// snip
}
return;
}
What I am trying to do is mock the get method on the $http service. Here's my unit test code:
describe('A first test suite', function(){
it("A trivial test", function() {
expect(true).toBe(true);
});
});
describe('Apps', function(){
describe('IndexCtrl', function(){
var scope, ctrl, $httpBackend;
var scope, http, cookies = {wtmdevsid:0};
beforeEach(inject(function($injector, $rootScope, $controller, $http) {
scope = $rootScope.$new();
ctrl = new $controller('IndexCtrl', {$scope: scope, $http: $http, $cookies: cookies});
spyOn($http, 'get');
spyOn(scope, 'modal');
}));
it('should create IndexCtrl', function() {
var quux = scope.modal();
expect(scope.modal).toHaveBeenCalled();
expect($http.get).toHaveBeenCalled();
});
});
});
When I run this I get
ReferenceError: wtm is not defined.
wtm is a global object and of course it wouldn't be defined when I run my test because the code that it is declared in is not run when I run my test. What I want to know is why the real $http.get function is being called and how do I set up a spy or a stub so that I don't actually call the real function?
(inb4 hating on globals: one of my coworkers has been tasked with factoring those out of our code :) )
You need to wire up the whenGET method of your $httpBackend in advance of your test. Try setting it up in the beforeEach() function of your test... There is a good example here under "Unit Testing with Mock Backend".
I suggest all globals used the way you described here should be used through the $window service.
All global variables that are available, such as as window.wtm, will also be available on $window.atm.
Then you can stub out your wtm reference completely and spy on it the same way you already described:
var element, $window, $rootScope, $compile;
beforeEach(function() {
module('fooApp', function($provide) {
$provide.decorator('$window', function($delegate) {
$delegate.wtm = jasmine.createSpy();
return $delegate;
});
});
inject(function(_$rootScope_, _$compile_, _$window_) {
$window = _$window_;
$rootScope = _$rootScope_;
$compile = _$compile_;
});
});
Maybe you could create a custom wrapper mock around $httpBackend that handles your special needs.
In detail, Angular overwrites components of the same name with a last-come first-served strategy, this means that the order you load your modules is important in your tests.
When you define another service with the same name and load it after the first one, the last one will be injected instead of the first one. E.g.:
apptasticMock.service("socket", function($rootScope){
this.events = {};
// Receive Events
this.on = function(eventName, callback){
if(!this.events[eventName]) this.events[eventName] = [];
this.events[eventName].push(callback);
}
// Send Events
this.emit = function(eventName, data, emitCallback){
if(this.events[eventName]){
angular.forEach(this.events[eventName], function(callback){
$rootScope.$apply(function() {
callback(data);
});
});
};
if(emitCallback) emitCallback();
}
});
This service offers the exact same interface and behaves exactly like the original one except it never communicates via any socket. This is the service we want to use for testing.
With the load sequence of angular in mind, the tests then look like this:
describe("Socket Service", function(){
var socket;
beforeEach(function(){
module('apptastic');
module('apptasticMock');
inject(function($injector) {
socket = $injector.get('socket');
});
});
it("emits and receives messages", function(){
var testReceived = false;
socket.on("test", function(data){
testReceived = true;
});
socket.emit("test", { info: "test" });
expect(testReceived).toBe(true);
});
});
The important thing here is that module('apptasticMock') gets executed after module('apptastic'). This overwrites the original socket implementation with the mocked one. The rest is just the normal dependency injection procedure.
This article I wrote could be interesting for you, as it goes into further details.
I've got a fairly straightforward function which returns a jQuery .ajax() promise as such:
CLAW.controls.validateLocation = function(val, $inputEl) {
return $.ajax({
url: locationServiceUrl + 'ValidateLocation/',
data: {
'locationName': val
},
beforeSend: function() {
$inputEl.addClass('busy');
}
}).done(function(result) {
// some success clauses
}).fail(function(result) {
// some failure clauses
}).always(function() {
// some always clauses
});
}
For the most part, this new promises interface works like a dream, and eliminating callback pyramids when using jQuery's .ajax() is great. However, I cannot for the life of me figure out how to properly test these promises using Jasmine and/or Sinon:
All of Sinon's documentation assumes you're using old-school
callbacks; I don't see a single example of how to use it with
promises/deferreds
When attempting to use a Jasmine or Sinon spy to spy on $.ajax, the
spy is effectively overwriting the promise, so its done, fail,
and always clauses no longer exist on the ajax function, so the promise never resolves and tosses an error instead
I'd really just love an example or two of how to test these new jQuery .ajax() promises with the aforementioned testing libs. I've scoured the 'net fairly intensely and haven't really dredged up anything on doing so. The one resource I did find mentioned using Jasmine.ajax, but I'd like to avoid that if possible, seeing as Sinon provides most of the same capabilities out-of-the-box.
It is not that complex actually. It suffices to return a promise and resolve it according to your case.
For example:
spyOn($, 'ajax').andCallFake(function (req) {
var d = $.Deferred();
d.resolve(data_you_expect);
return d.promise();
});
for a success, or
spyOn($, 'ajax').andCallFake(function (req) {
var d = $.Deferred();
d.reject(fail_result);
return d.promise();
});
for a failure.
For Jasmine 2.0 the syntax has changed slightly:
spyOn($, 'ajax').and.callFake(function (req) {});
the method .andCallFake() does not exist in Jasmine 2.0
something along these lines / with sinon and jQuery deferreds
ajaxStub = sinon.stub($, "ajax");
function okResponse() {
var d = $.Deferred();
d.resolve( { username: "testuser", userid: "userid", success: true } );
return d.promise();
};
function errorResponse() {
var d = $.Deferred();
d.reject({},{},"could not complete");
return d.promise();
};
ajaxStub.returns(okResponse());
ajaxStub.returns(errorResponse());
Here's a simpler approach with just javascript.
quoteSnapshots: function (symbol, streamId) {
var FakeDeferred = function () {
this.error = function (fn) {
if (symbol.toLowerCase() === 'bad-symbol') {
fn({Error: 'test'});
}
return this;
};
this.data = function (fn) {
if (symbol.toLowerCase() !== 'bad-symbol') {
fn({});
}
return this;
};
};
return new FakeDeferred();
}
The if statements inside of each callback are what I use in my test to drive a success or error execution.
The solution given by #ggozad won't work if you use things like .complete().
But, hooray, jasmine made a plugin to do exactly this: http://jasmine.github.io/2.0/ajax.html
beforeEach(function() {
jasmine.Ajax.install();
});
afterEach(function() {
jasmine.Ajax.uninstall();
});
//in your tests
expect(jasmine.Ajax.requests.mostRecent().url).toBe('/some/cool/url');