I've hit a very weird problem: I'm trying to make unit tests to achieve 100% testing coverage on my application.
And of course I wrote some tests for my controllers but it seems like there is no way to test anything async in Ember (2.4.0) using ember-cli.
I have a function in controller that does this:
readObject() {
this.store.findRecord('myModel',1).then(function(obj) {
this.set('property1',obj.get('property2');
}.bind(this));
}
I'm writing a test that should cover this function.
test('action readObject', function (assert) {
const cont = this.subject();
cont.readObject();
assert.equal(cont.get('property1'), 'someValue);
});
Obivously, this assert wouldn't work because readObject() is async call but this isn't the root of the problem. The problem is that then a callback in this.store.findRecord is being executed - my controller is already destroyed! So I get "calling set on destroyed object" error there.
In other words - even if I wrap my function in a promise and reformat both functions like this:
readObject() {
return new Promise(function(resolve) {
this.store.findRecord('myModel',1).then(function(obj) {
this.set('property1',obj.get('property2');
resolve();
}.bind(this));
}.bind(this));
}
and
test('action readObject', function (assert) {
const cont = this.subject();
cont.readObject().then(function() {
assert.equal(cont.get('property1'), 'someValue);
});
});
It wouldn't work, because after executing readObject() my controllers become immediately destroyed, not waiting for any callbacks.
So, it could be any async call instead of store.findRecord - it could be Ember.run.later, for example.
Does anybody had the same issue? I've read a lot of articles I can't believe that Ember with such a big community doesn't provide a way to make async unit tests.
If anyone has any clues - please give me a hint, cause I'm kinda lost here. At the moment I have two thoughts:
I'm making controllers wrong, Ember doesn't suppose any async operations inside of it. But even if I move async calls to services - I hit the same problem with writing unit tests for them.
I have to decompose my functions to
readObject() {
this.store.findRecord('myModel',1).then(this.actualReadObject.bind(this));
}
actualReadObject(obj) {
this.set('property1',obj.get('property2');
}
to have at least callbacks body covered with tests, but this means I never get 100% testing coverage in my app.
Thank you in advance for any clues. :)
I had a similar problem and looking at the QUnit API - async I solved it. Try out following:
// ... in your controller ...
readObject() {
return this.store.findRecord('myModel',1).then(function(obj) {
this.set('property1', obj.get('property2');
}.bind(this));
}
// ... in your tests, either with assert.async: ...
const done = assert.async(); // asynchronous test due to promises usage
Ember.run(function(){
subject.readObject().then(function(){
// once you reach this point you are sure your promise has been resolved
done();
});
});
// or without assert.async
let promise;
Ember.run(function(){
promise = subject.readObject();
});
return promise;
In a case of unit tests I do also mock other dependencies, for example: store.
this.subject({
property1: null,
store: {
findRecord(modelName, id){
assert.equal(modelName, "myModel1");
assert.equal(id, 1);
return new Ember.RSVP.Promise(function(resolve){
resolve(Ember.Object.create({ property2: "a simple or complex mock" }));
})
}
}
});
I am not sure about the second case (the one without assert.async). I think it would work too, because the test suite returns a promise. This gets recorgnized by QUnit that waits for the promise.
I copy my own solution here, cause code formatting in comments isn't too good.
test('async', function (assert) {
const cont = this.subject();
const myModel = cont.get('store').createRecord('myModel'); //
// Make store.findRecord sync
cont.set('store',{
findRecord(){
return { then : function(callback) { callback(myModel); } }
}
});
// Sync tests
assert.equal(cont.get('property2'), undefined);
cont.readObject(); // This line calls store.findRecord underneath
assert.equal(cont.get('property2'), true);
});
So, I just turned store.findRecord into a sync function and it runs perfect. And again - many thanks to Pavol for a clue. :)
Related
I have TDD knowledge, and I've been trying to start a project in javascript applying the same principles.
I am building an API that, once hit, fires a request to an external service in order to gather some data, and once retrieved, parses it and returns the response.
So far, I've been unlucky with my saga, I've searched a lot and the most similar issue on SO I've found is this one. But I've had no success in order to apply the same solution.
My implementation on the implementation side is as follows:
//... other handlers
weather(request, response) {
//... some setup and other calls
this.externalService.get(externalURL)
.then(serviceResponse => {
this.externalResponseParser.parse(serviceResponse)
});
//... some more code
}
And on the test side:
let requester;
let mockParser;
let handler;
let promiseResolve;
let promiseReject;
beforeEach(function () {
requester = new externalRequesterService();
mockParser = sinon.createStubInstance(...);
handler = new someHandler({
externalService: requester,
externalResponseParser: mockParser
});
});
it('returns data', function () {
sinon.stub(requester, 'get').callsFake((url) => {
return new Promise((resolve, reject) => {
// so I can be able to handle when the promise gets resolved
promiseResolve = resolve;
promiseReject = reject;
});
});
handler.weather(request, response);
// assertions of what happens before externalService.get gets called - all green
promiseResolve(expectedServiceResponse);
assert.ok(mockExternalResponseParser.parse.calledOnce, 'Expected externalResponseParser.parse to have been called once');
});
In the last line of the test, it fails, even though I am calling what I am supposed to.
At some point I've added some logging, and I was able to see that the code of the then block, seems to get executed after the assertion in the test, which might be source of the problem.
I've tried to find out if there is some sort of eventually that could be used, so my assertion after resolving the promise would be something like:
assert.eventually(mockExternalResponseParser.parse.calledOnce, 'Expected externalResponseParser.parse to eventually be called once');
but no luck.
Does anyone have some clear explanation of what is missing? Many thanks in advance
P.S.- As per request, please find a stripped down version of my code here. Just run npm install, followed by npm test in order to get the same output.
Thank you all for the time spent.
I ended up finding this very good article on Medium which allowed me to solve my issue. It has a very nice explanation moving from callbacks to promises, which was exactly the scenario I had in hands.
I have updated the github repository I created on how to fix it.
If you want to TL;DR here goes just the highlight of the changes. Implementation side:
async weather(request, response) {
//...
let serviceResponse = await this.requesterService.get(weatherURL);
//...
};
And on the test side:
it('returns weather on success', async function () {
sinon.stub(requester, 'get').callsFake((_) => {
return new Promise((resolve, _) => {
resolve(expectedServiceResponse);
});
});
await handler.weather(request, response);
//...
assert.ok(mockWeatherParser.parseWeather.calledOnce, 'Expected WeatherParser.parseWeather to have been called once'); // no longer fails here
//...
});
Now keep in mind in this example, it is still synchronous. However, I evolved my API already step by step, and after migrating to this synchronous version using Promises, it was way more easier to migrate to an async version. Both in terms of testing and implementation.
If you have the same issue and need help or have questions, let me know. I'll be happy to help.
I'm sure the answer to this question exists somewhere, but unsure how to phrase the question and don't find what I'm looking for when I try to research callbacks.
Ok, so I've just started dabbling with Gulp for compiling my CSS, and I think it's great!.. despite being useless with JavaScript.
My code is below, but I don't understand how these callbacks are working. How can callbacks be set as a parameter and then be called from inside the function? I don't get it.. is the function basically expecting something to be in there? What value is being set or what's expected to run? I can't seem to make sense of it.
I see this quite frequently in JavaScript, but unsure how it works. I've looked up videos and tutorials of functions and callbacks, they makes sense, but I never seem to be able to find anywhere where this concept is explained which makes me believe I'm not looking for the right thing.
I see the same sort of thing with Promises as well where 'resolve' or 'reject' parameters are set, but unsure what's going on when a condition is met, or what values are set and where.. hope this makes sense and appreciate any help in understanding this better.
const gulp = require('gulp');
const sass = require('gulp-sass');
const browserSync = require('browser-sync');
// TASKS
// Compile SASS
gulp.task('sass-compile', (callback) => {
gulp.src('scss/*.scss')
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest('css'))
.pipe(browserSync.stream());
console.log('******************** SCSS > CSS successful ********************');
callback();
});
// Live reload
gulp.task('browser-sync', (callback) => {
browserSync.init({
proxy: 'http://localhost/test1',
port: 80
});
callback();
});
// WATCHER
gulp.task('default', gulp.series('browser-sync', (callback) => {
gulp.watch('scss/*.scss', gulp.series('sass-compile'));
callback();
}));
function foo(baz) {
console.log("Foo: " + baz);
}
function other(bar) {
const arg = "Bar";
bar(arg);
}
other(foo);
I can see how this is useful when it comes to performing asynchronous tasks. But its quite counter intuitive and I can immediately see the potential for type errors.
I've since changed my coding style to use ASYNC/AWAIT instead as much as possible. But it still a pain to navigate legacy code. Hence, the reason why I ended up looking up this thread. Haha
Functions are a type of object. Objects are a type of value.
Values can be stored in variables, in object properties, and passed as arguments to functions.
So:
Here is a simple function, which is called.
function foo() {
console.log("Foo");
}
foo();
Here is a simple function, copied to a different variable, which is called.
function foo() {
console.log("Foo");
}
const bar = foo;
bar();
And now passed as an argument to another function.
function foo() {
console.log("Foo");
}
function other(bar) {
bar();
}
other(foo);
And the same, but with arguments passed to the original function.
function foo(baz) {
console.log("Foo: " + baz);
}
function other(bar) {
const arg = "Bar";
bar(arg);
}
other(foo);
You've just been looking at examples where the function responsible for calling the callback is not code you've written (and you aren't looking at the source code of the function which calls the callback).
In javascript, functions are first class objects, which means they can be passed around to other functions. There are many cases where this can be used, but the cases you're referring to (gulp.task and new Promise) are essentially using it to do two-way communication between your code and their code. When you use gulp.task, basically the following steps are happening
1) You call gulp.task, saying "i'd like to do some work please". You pass in a function saying what you'd like to do
2) at the appropriate time, gulp.task calls your function, but passes in to it another function saying "when you're done, call this to let me know".
A simplified version of gulp.task would look like this:
const task = (name, workToDo) => {
const done = () => {
console.log('task', name, 'has let me know its done');
}
console.log('ive been asked to start task', name);
setTimeout(() => {
console.log('500ms elapsed; starting task', name);
workToDo(done);
}, 500);
}
task('sample', (done) => {
console.log('doing work');
setTimeout(() => {
console.log('1 second has elapsed. calling back to let it know i am done');
done();
}, 1000)
});
The constructor for a promise has a similar purpose. You say "i'd like to create a promise, using the following code", and then it calls your code passing in two functions that it just created (usually named resolve and reject). These are used to tell the promise "i'm done successfully" or "i'm done with an error", and thus move the promise to a resolved state or a rejected state.
I'm trying to write a test in JavaScript, the method I'm testing makes 2 method calls (model.expandChildren() and view.update();)
// inside 'app' / 'RV.graph'
var expandChildren = function(){
return model.expandChildren().then(function(r) {
view.update(r);
});
};
I've tried to use Jasmine specs to write the test to spyOn both the view and model functions, but it appears you can only have 1 spy in a given test. It seems I'm missing something big here and that there should be a way to mock out multiple methods calls using spies since my function needs to make both of these calls.
I want my spec to be able to run the way it is below, but it currently only passes the first test (the first spy runs as expected), the second test fails because Jasmine is trying to run the actual function, not the spied function:
var model = GRAPH.model;
var view = GRAPH.view;
var app = RV.graph;
describe('#expandChildren', function() {
beforeEach(function() {
// first spy, all good
spyOn(model, 'expandChildren').and.callFake(function() {
return new Promise(function(resolve) {
resolve(testResponse);
});
});
// second spy doesn't work because Jasmine only allows 1
spyOn(view, 'update');
app.expandChildren();
});
// passing test
it('calls model.expandChildren', function() {
expect(model.expandChildren).toHaveBeenCalled();
});
// failing test that runs the REAL view.update method
it('calls view.update', function() {
expect(view.update).toHaveBeenCalled();
});
});
Is there a way to do this with Jasmine?
Remember that you are working with asynchronous calls. The first call is synchronous, so it is recorded, but the second one only happens later. Give yourself some control over when things happen. I commonly use a pattern like this:
describe('#expandChildren', function() {
var resolver;
it('calls model.expandChildren', function(done) {
spyOn(model, 'expandChildren').and.callFake(function() {
return new Promise(function(resolve) {
resolver = resolve;
});
});
spyOn(view, 'update');
app.expandChildren();
expect(model.expandChildren).toHaveBeenCalled();
expect(view.update).not.toHaveBeenCalled();
resolver();
done();
expect(view.update).toHaveBeenCalled();
});
});
This way, the spec will only be run after the promise has been resolved and done() has been called.
I have a real problem with JavaScript promises that I've been trying to solve for the last few hours and I just can't seem to fix it. My experience with promises is limited so I'm open to the idea that my approach is simply incorrect.
Right now I'm building an app that requires a two-step process:
Connect to an external PaaS service, which returns a promise
Within that promise, retrieve some data
Here's a sample of a factory I created:
app.factory('serviceFactory', [
function() {
var getData = function getData() {
service.connect(apiKey).then(function() {
service.getData('dataStore').then(function(result) {
// Retrieve data
return result;
}, errorFunction);
},
errorFunction);
};
return {
getData: getData
};
}
]);
As you can see, there are nested promises here. What's causing me problems is when I try to use the data from the most deeply-nested promise within an AngularJS view. Specifically, I want to use the data from that promise in an ng-repeat statement. But no matter what I try, it just won't show up. I've attempted to assign data within the promise instead of returning, like so:
service.getData('dataStore').then(function(result) {
// Retrieve data
// Assigned the enclosing scope's this to 'self'
self.data = result;
}, errorFunction);
That doesn't work either. I've tried a variety of other approaches, but I just can't seem to get that data to the view. There's no problem getting it to show up in a console.log(data)call, so I know the data is coming back correctly. Does anyone have experience solving a problem like this?
I would suggest that you'll try to avoid nested promises. You can take a look at this blog post, which will let you see how you can avoid 'promise soup' and have promise chaining instead.
As for your question, I would recommend the following:
A quick solution will be to fix your problem. You are returning the factory method wrong:
app.factory('serviceFactory', [
function() {
var getData = function getData() {
return service.connect(apiKey).then(function() {
service.getData('dataStore').then(function(result) {
// Retrieve data
return result;
}, errorFunction);
},
errorFunction);
};//here you should close the 'getData method
return {
getData: getData
};
}
]);
But, you can refactor your code to chain your promises. Something like:
app.factory('serviceFactory', [
function() {
var connect = function connect() {
return service.connect(apiKey);
};
var getData = function getData(data) {
return service.getData(data);
};
return {
getData: getData,
connect: connect
};
}
]);
Now, you can do something like this:
serviceFactory.connect(apiKey)
.then(serviceFactory.getData)
.then(function(result){
//use data here
})
All of this should be tested - you can add a plunker or jsbin if you want a working solution...
EDIT
I think that you have another problem here. You are mixing between serviceFactory and service. I'm not sure that I understand if this is the same service, or which is who. Can you provide a more detailed code or add plunker/jsbin etc.
I've edited this answer, which I originally deleted because I didn't explain what I meant very clearly and it garnered some downvotes (without explanation, but that's my guess). Anyway, here is a more complete answer.
I suspect that your problem is that whatever PaaS you are using has no awareness of Angular, and Angular likewise has no awareness of the PaaS. You say in your question that the PaaS has methods that return promises, but if Angular is not aware of those promises, then, when the promises resolve, Angular does not know to update the DOM. Angular does this via the digest cycle which is where Angular checks everything that it is watching to see if it has changed. When using $q (or other Angular services like $http), Angular knows to automatically kick off a digest cycle when they resolve. It does not, however, kick off a digest cycle when promises created by other means resolve.
This is what I think is happening in your code. Your PaaS is giving you promises, which are resolving properly (you said you can see the results via console), but your HTML is not being updated.
I modified the plunkr we were working on to demonstrate this in action. I created a mock PaaS (not knowing what you are using) that creates promises using jQuery and resolves them. As you can see, when the promises resolve, the result is logged to the console, but the DOM is not resolved.
angular.module("app",[])
.value("mockPaaS", mockPaaS)
.factory("dataFactory", function($q, mockPaaS){
function getData(){
return mockPaaS.connect()
.then(mockPaaS.getData);
}
return {
getData: getData
}
})
.controller("DataController", function (dataFactory) {
var vm = this;
dataFactory.getData().then(function(result){
console.log(result);
vm.dataArr = result;
});
})
.directive("myApp", function(){
return {
bindToController: true,
controller: "DataController",
controllerAs: 'myApp',
template: "<div ng-repeat='i in myApp.dataArr'>{{i}}</div>"
};
});
I was originally suggesting that you could solve this problem by adding a $scope.$apply() after you capture the result of the promise. I've forked the Plunker and you can see here it does, in fact update the DOM.
.controller("DataController", function ($scope, dataFactory) {
var vm = this;
dataFactory.getData().then(function(result){
console.log(result);
vm.dataArr = result;
$scope.$apply();
});
})
There is, however, a more idiomatic solution. When you get a promise from outside angular that you need to use in Angular, you can wrap that promise using $q.when (an Angular aware promise), and when the external promise resolves, Angular should kick off it's digest cycle naturally.
.factory("dataFactory", function($q, mockPaaS){
function getData(){
return $q.when(mockPaaS.connect()
.then(mockPaaS.getData));
}
return {
getData: getData
}
})
.controller("DataController", function (dataFactory) {
var vm = this;
dataFactory.getData().then(function(result){
console.log(result);
vm.dataArr = result;
});
})
Ben Nadel gives a nice explanation of this issue here.
I am trying to unit test an Angular.js service, and need to set an expect on a promise returned from a Mock service (using Jasmine). I am using the karma unit testing framework. The relevant code snippet is below:
// I can't figure out how to do the equivalent of a $scope.$digest here.
var loginStatusPromise = FacebookService.getFacebookToken();
loginStatusPromise.then(function(token) {
expect(false).toBeTruthy(); // If this test passes, there is something going wrong!
expect(token).not.toBeNull(); // The token should be ValidToken
expect(token).toBe('ValidToken');
});
The complete unit test code can be seen here.
The problem is the promise.then statement never fires when karma is executing. Hence, none of my expect statements are executed.
In my controller tests, I use $scope.$digest() to resolve the promises, but I am not clear on how to do this in a service test. As I thought there was no notion of 'scope' in a service test.
Do I have the wrong end of the stick here? Do I need to injecct $rootScope into my service test and then use $digest? Or, is there another way?
I had this problem and resolved it by simply putting a
$rootScope.$apply() at the end of my test
Your FacebookService might be the issue, as suggested by #mpm. Are you sure it doesn't have any http calls happening inside of that Facebook dependency which wouldn't be occurring during unit testing? Are you certain that resolve has been called on the deferred yet?
Assuming that you are using ngFacebook/ngModule a quick note before the solution/ideas is that this project does not have unit tests ! Are you sure you want to use this project ?
I did a quick scan of your Unit Tests on Github and found following missing:-
1) Module initialization.
ngFacebook needs that or you need to initialize your module that does the same thing.
beforeEach(module('ngFacebook'));
OR
beforeEach(module('yieldtome'));
2) Seriously consider mocking ngFacebook module
At unit level tests you are testing your code within a mocked bubble where outside interfaces are stubbed out.
Otherwise) Try adding calling the API as below:-
$rootScope.$apply(function() {
this.FacebookService.getFacebookToken().then(function(){
//your expect code here
});
});
$httpBackend.flush();//mock any anticipated outgoing requests as per [$httpBackend][2]
beforeEach(function(){
var self=this;
inject(function($rootScope,Facebook){
self.$rootScope=$rootScope;
self.Facebook=Facebook;
});
})
it('resolves unless sourcecode broken',function(done){
// I can't figure out how to do the equivalent of a $scope.$digest here.
var loginStatusPromise = this.FacebookService.getFacebookToken();
loginStatusPromise.then(function(token) {
expect(token).toBe('ValidToken');
done();
});
$rootscope.$apply();
});
https://docs.angularjs.org/api/ng/service/$q
I agree with the above answers that a service should have nothing to do with $rootScope.
In my case had a $q promise, that used a second service internally resolving to a promise as well. No way to resolve the external one, unless I added $rootScope.$digest() into my service code (not the test)...
I ended-up writing this quick shim for $q to use in my tests, but be careful, as it's just an example and not a complete $q implementation.
beforeEach(module(function($provide) {
$provide.value('$q', {
defer: function() {
var _resolve, _reject;
return {
promise: {
then: function (resolve, reject) {
_resolve = resolve;
_reject = reject;
}
},
resolve: function (data) {
window.setTimeout(_resolve, 0, data);
},
reject: function (data) {
window.setTimeout(_reject, 0, data);
}
};
}
});
}));
Hope it will be useful to someone, or if you have any feedback.
Thanks.