Unit testing logic inside promise callback - javascript

I have an ES6 / Aurelia app that I am using jasmine to test. The method I am trying to test looks something like this:
update() {
let vm = this;
vm.getData()
.then((response) => {
vm.processData(response);
});
}
Where this.getData is a function that returns a promise.
My spec file looks something like this:
describe('my service update function', () => {
it('it will call the other functions', () => {
myService = new MyService();
spyOn(myService, 'getData').and.callFake(function() {
return new Promise((resolve) => { resolve(); });
});
spyOn(myService, 'processData').and.callFake(function() { return; });
myService.update();
// this one passes
expect(myService.getData).toHaveBeenCalled();
// this one fails
expect(myService.processData).toHaveBeenCalled();
});
});
I understand why this fails - promises are asynchronous and it hasn't been resolved by the time it hits the expect.
How can I push the promises to resolve from my test so that I can test the code inside the call back?
jsfiddle of failed test: http://jsfiddle.net/yammerade/2aap5u37/6/

I got a workaround running by returning an object that behaves like a promise instead of an actual promise
describe('my service update function', () => {
it('it will call the other functions', () => {
myService = new MyService();
spyOn(myService, 'getData').and.returnValue({
then(callback) {
callback();
}
});
spyOn(myService, 'processData').and.callFake(function() { return; });
myService.update();
// this one passes
expect(myService.getData).toHaveBeenCalled();
// this one fails
expect(myService.processData).toHaveBeenCalled();
});
});
Fiddle here: http://jsfiddle.net/yammerade/9rLrzszm/2/
Is there anything wrong with doing it this way?

it((done) => {
// call done, when you are done
spyOn(myService, 'processData').and.callFake(function() {
expect(myService.processData).toHaveBeenCalled();
done();
});
})

Related

How to force Jest unit test assertion to execute before test ends?

I have the following test in my react native application, but the test should fail (because the action returned is not equal to the action I put in expectedActions. My guess is that it is passing because the expect test runs after the test has completed.
How can I force the test to wait until the promise is completed and the expect test runs? Is there another way of doing this?
describe('authorize actions', () => {
beforeEach(() => {
store = mockStore({});
});
it('should create an action to signify successful auth', () => {
auth.authorize.mockImplementation(() => Promise.resolve({"something": "value"}));
const expectedActions = [{"type":"AUTHORIZE_RESPONSE","payload":{"something":"sdklfjsdf"}}];
authorizeUser(store.dispatch, store.state).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
})
});
Ok, looks like I just missed some of the Jest docs - if you return the promise, i.e. return auth.authorize.mockImplementation(() => Promise.resolve... then Jest will wait until it's completed before continuing.
The are varios ways to test async code. Check the docs for examples: https://facebook.github.io/jest/docs/en/asynchronous.html
One could be returning the promise:
describe('authorize actions', () => {
beforeEach(() => {
store = mockStore({});
});
it('should create an action to signify successful auth', () => {
auth.authorize.mockImplementation(() => Promise.resolve({"something": "value"}));
const expectedActions = [{"type":"AUTHORIZE_RESPONSE","payload":{"something":"sdklfjsdf"}}];
return authorizeUser(store.dispatch, store.state).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
})
});

How do you use unit test a factory using $provide and $q

I'm trying to write a unit test for a service that has a dependency on another factory function that returns a promise. I've followed the process as seen in this question and answer, but for some reason the code in the then() that I'm trying to verify isn't being called. How do I get this to work? I'd like to use the version that is not doing the spyOn if possible (the Update section in the answer).
var app = angular.module('test', []);
app.factory('managerService', (managerModel) => {
var service = {
getCurrentUser: getCurrentUser
}
function getCurrentUser() {
return managerModel.api.get();
}
return service;
})
describe('promise with provider', () => {
var managerModelMOCK = {
api: {}
};
var $q;
var $rootScope;
var deferredUser;
// beforeEach(inject(()=> {})) //no inject here: Injector already created, can not register a module!
beforeEach(angular.mock.module('test'));
beforeEach(() => {
angular.mock.module(function ($provide) {
$provide.value('managerModel', managerModelMOCK); // override version here
});
});
beforeEach(inject(($q) => {
deferredUser = $q.defer();
managerModelMOCK.api.get = () => {
return deferredUser.promise;
}
}));
it('should resolve a promise', angular.mock.inject((managerService, $rootScope) => {
expect(managerService).toBeDefined();
var expected = {
Name: 'Somebody'
};
var actual;
//this doesn't work either
// var innerPromise = $q.defer();
// innerPromise.resolve(expected);
// managerModel.api.get = () => innerPromise.promise;
deferredUser.resolve(expected);
$rootScope.$apply();
managerService.getCurrentUser()
.then((user) => {
actual = user;
});
//undefined, why?
expect(actual).toBeDefined();
}))
})
Ugh, promises rookie mistake. the then() functions in a promise chain need to return, so this code works:
managerService.getCurrentUser()
.then((user) => {
actual = user;
//promise chains have to return
return;
});
//rootscope.$apply() had to be moved
$rootScope.$apply();
I had tried putting the `$rootScope.$apply() after the promise chain before, but of course not having the return in there still kept that from functioning.

Randomly failing JS tests

Hi I'm getting this error when running my tests I've read up on the promises and done I'm still unsure where to put it in my test or is it best to do a before each instead to save repetition? Where and what would be the best way to implement the done promise?
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure
"done()" is called; if returning a Promise, ensure it resolves.
const chakram = require('chakram');
const expect = chakram.expect;
describe("Json assertions", function () {
it("Should return the matching test file and have a 200 response", function () {
let expected = require('../../test/fixtures/testfile.json');
let response = chakram.get("http://test");
expect(response).to.have.json(expected);
expect(response).to.have.status(200);
return chakram.wait();
});
});
I am not familiar with chakram, but generally here is a pattern that should work with testing your promises (using done):
describe('Test Suite', function() {
it('should do something...', function(done) {
const testValue = 'testValue';
someAsyncFunction(testValue).then(function(result) {
result.should.have.property('foo');
result.should.have.property('bar');
done();
});
]);
});
Now for what you have, it looks like the docs for Chakram show how to test with promises (under the Promises header). So your adapted code would be something like:
const chakram = require('chakram');
const expect = chakram.expect;
describe("Json assertions", function () {
it("should...", function () {
let expected = require('../../test/fixtures/testfile.json');
chakram.get("http://test").then(function(response) {
expect(response).to.have.json(expected);
expect(response).to.have.status(200);
});
});
});
Again, I do not know that library, but if your test runner still complains about the done, add done like so:
describe("Json assertions", function () {
it("should...", function (done) {
let expected = require('../../test/fixtures/testfile.json');
chakram.get("http://test").then(function(response) {
expect(response).to.have.json(expected);
expect(response).to.have.status(200);
done();
});
});
});
Edit: done was in describe block, and should have been in it.

Jasmine - How to set a timeout for a describe block?

I have a class which is poorly written, and get initialized only after a short timeout. (It's dependent in a 3rd party loading, so it's pointless to offer modifying the code. thank you)
Nevertheless, it needs to be unit tested.
What I have right now is the following structure:
describe('my tests', function() {
var timeoutPromise = function () {
/... return promise .../
}
it('test1', function (done) {
timeoutPromise.then(function() {
expect(...);
done();
});
});
it('test2', function (done) {
timeoutPromise.then(function() {
expect(...);
done();
});
});
});
That way I make sure all tests run after a few ms timeout.
Is there is way to make the whole describe block run only after the timeout?
Something like
describe('my tests', function(done) {
it(...);
it(...);
done();
}
There's no describe() level done callback. From the Jasmine 2.5 docs:
Calls to beforeAll, afterAll, beforeEach, afterEach, and it can take an optional single argument that should be called when the async work is complete.
However, you can do one time initialization in a beforeEach() on the same level as the it() blocks they belong to.
The following code will fullfill your requirements. It is inline commented:
describe('your tests', function() {
var timeoutPromise = new Promise(function(resolve, reject){
setTimeout(resolve, 4000);
});
// execute timeoutPromise and wait for it to resolve ...
beforeEach(function(done) {
timeoutPromise.then(function() {
// timeout resolved, test initialization in this block done
done();
});
});
// for all following it()s the beforeEach() needs to complete
it('test1', function () {
expect(...);
});
it('test2', function () {
expect(...);
});
// ...
});

Jasmine: How to expect promise handler to not throw exception

I have this function:
reload() {
myService.queryData()
.done(...)
.always(() => throw "fake exception"); //just to simulate the failure
}
I want my test reload function and make sure it does not throw exception nor the promise callback does.
describe("reload", function () {
it("does not throw exception", function (done) {
spyOn(myService, "queryData").and.callFake(() => {
let deffered = $.deffered();
setTimeOut(() => deffered.reject(), 0)
return deffered.promise();
});
reload();
setTimeout(() => {
//this is evaluated after the exception has been thrown, but
//how to check whether exception has been thrown
}, 2);
});
});
EDIT: I might not be able to return a promise in some cases, where the return type of the function is already defined, e.g component's lifecycle event:
MyComponent extends React.Component {
componentDidMount() {
this.load(
galleryService.nodes().then(galleryResult => this.setState({ nodes: galleryResult.nodes }))
);
this.load(
galleryService.caches().then(cachesResult => this.setState({ caches: cachesResult.caches }))
);
}
}
var myComponent = React.createElement(MyComponent);
TestUtils.renderIntoDocument(myComponent); //this triggers the componentDidMount event and I need to make sure it won't throw error.
Let reload return the promise it creates. In your test case, attach a catch handler to it which triggers a test failure:
reload().catch(err => done.fail(err));
Update after question was edited: If you cannot change the return value of your original function then factor out the relevant parts into separate functions. For example:
function reloadNodes() {
return somePromise();
}
function reloadCaches() {
return anotherPromise();
}
function reload() {
reloadNodes();
reloadCaches();
}
You can then test reloadNodes and reloadCaches instead of reload. Obviously you don't need to create a separate function for each promise, instead combine your promises using something like Promise.all where appropriate.
I believe that spying on window.onerror is the way to go:
describe("reload", function () {
it("does not throw an exception", function (done) {
spyOn(window, 'onerror').and.callFake((error: any, e: any) => {
fail(error);
});
spyOn(myService, "queryData").and.callFake(() => {
let deffered = $.deffered();
setTimeout(deffered.reject, 0);
return deffered.promise();
});
});
setTimeout(done, 2);
});
});

Categories

Resources