Vue with jest - Test with asynchronous call - unit-testing

How to make my test wait for the result of my api?
I'm using vue and jest to test my components.
I want to test the method that writes a client to my database. In my component I have the following method:
methods: {
onSubmitClient(){
axios.post(`urlApi`, this.dados).then(res => {
return res;
})
}
}
in my test
describe('login.vue', () => {
let wrapper
beforeAll(()=>{
wrapper = mount(client, {
stubs: ['router-link'],
store,
data() {
return {
dados: {
name: 'tes name',
city: 'test city'
},
};
}
})
})
it('store client', () => {
res = wrapper.vm.onSubmitLogin()
console.log(res);
})
})
My test does not wait for the API call to complete. I need to wait for the API call to know if the test worked. How can I make my test wait for API return?

There are several issues in your code.
First, you cannot return from an async call. Instead, you should be probably setting up some data in your onSubmitClient, and returning the whole axioscall, which is a Promise. for instance:
onSubmitClient(){
return axios.post(`urlApi`, this.dados).then(res => {
this.result = res;
return res;
})
}
I assume the method here is storing a result from the server. Maybe you don't want that; it is just an example. I'll come back to it later.
Ok, so now, you could call onSubmitClient in your wrapper and see if this.result is already set. As you already know, this does not work straightforward.
In order for a jest test to wait for asynchronous code, you need either to provide a done callback function or return a promise. I'll show an example with the former:
it('store client', (done) => {
wrapper.vm.onSubmitLogin().then((res) => {
expect(wrapper.vm.dados).toEqual(res);
done();
})
});
Now this code should just work, but still there is an issue with it, as #jonsharpe says in a comment.
You usually don't want to perform real network requests in unitary tests because they are slow and unrealiable. Also, unitary tests are meant to test components in isolation, and here we are testing not only that our component sets this.result properly when the request is made. We are also testing that there is a webserver up and running that is actually working.
So, what I would do in this scenario to test that single piece of functionality, is to extract the request to another method, mock it with vue-test-utils and jest.fn, and then assert that onSubmitClient does its work:
The component:
export default {
data() {
return {
http: axios,
...
},
methods: {
onSubmitClient(){
this.http.post(`urlApi`, this.dados).then(res => {
this.result = res;
})
}
}
}
}
The test:
it('store client', (done) => {
const fakeResponse = {foo: 'bar'};
var post = jest.fn();
var http : {
post,
};
var wrapper = mount(client, {
stubs: ['router-link'],
store,
data() {
return {
dados: {
name: 'tes name',
city: 'test city'
},
http, //now, the component under test will user a mock to perform the http post request.
}
}
});
wrapper.vm.onSubmitLogin().then( () => {
expect(post).toHaveBeenCalled();
expect(wrapper.vm.result).toEqual(fakeResponse);
done();
})
});
Now, your test asserts two things:
post gets called.
this.result is set as it should be.
If you don't want to store anything in your component from the server, just drop the second assertion and the this.result = res line in the method.
So basically this covers why your test is not waiting for the async request and some issues in your code. There are still some things to consider (f.i. I think a global wrapper is bad idea, and I would always prefer shallowMount over mount when testing components behavior), but this answer should help you a lot.
PS: didn't test the code, so maybe I messed up something. If the thing just doesn't work, look for syntax errors or similar issues.

Related

How to unit test if Vuex action has been dispatched in VueJS

I just want to test if myAwesome action is dispatched when my App.vue component is created(). Is this something you would test? I'm using Jasmine for these tests. Any help would be awesome!
App.js
describe('when app is created()', () => {
it('should dispatch myAwesomeAction', (done) => {
const actions = {
myAwesomeAction() {
console.log('myAwesomeAction')
// commit()
}
}
const state = {
myAwesomeAction: false
}
const mutations = {
LOAD_SUCCESS(state) {
state.myAwesomeAction = true
}
}
const options = {
state,
mutations,
actions
}
const mockStore = new Vuex.Store(options)
spyOn(mockStore, 'dispatch')
const vm = new Vue({
template: '<div><component></component></div>',
store: mockStore,
components: {
'component': App
}
}).$mount()
Vue.nextTick(() => {
expect(mockStore.dispatch).toHaveBeenCalled()
expect(mockStore.dispatch).toHaveBeenCalledWith('myAwesomeAction')
done()
})
})
})
Errors:
1) should dispatch myAwesomeAction
App
Expected spy dispatch to have been called.
webpack:///src/views/App/test/App.spec.js:49:6 <- index.js:50916:50
webpack:///~/vue/dist/vue.esm.js:505:15 <- index.js:3985:24
nextTickHandler#webpack:///~/vue/dist/vue.esm.js:454:0 <- index.js:3934:16
Expected spy dispatch to have been called with [ 'loadOrganisation' ] but it was never called.
webpack:///src/views/App/test/App.spec.js:50:54 <- index.js:50918:54
webpack:///~/vue/dist/vue.esm.js:505:15 <- index.js:3985:24
nextTickHandler#webpack:///~/vue/dist/vue.esm.js:454:0 <- index.js:3934:16
The thing is, you are trying to unit test a store from a component, so there's a little bit of a problem when you are mocking some elements and relying in true functionality in other elements. I'm no expert in vuex, I had a similar problem trying to spy on a store action and call a component's method (can't remember what the problem was, i remember i did waste half a day with it).
My suggestion: test component as unit, then test store module as unit, that means in your app component you can spy
spyOn(vm, 'myAwesomeAction');
Vue.nextTick(() => {
expect(vm.myAwesomeAction).toHaveBeenCalled()
done()
});
(that is, check if initializing your component, your method that calls the store action is called, in my example myawesomeaction will be a mapAction name in the methods object)
And then, you can unit test your store, and check that if you call myawesomeaction the mutation on that component will occur
check the test action helper here: https://vuex.vuejs.org/en/testing.html

Testing observable object angular 2 karma

I'm working on my unit test cases for Angular 2 with Karma, I got stuck with one of a function where I run the test for below line
expect(component.subscribeToEvents()).toBeTruthy();
and I view my coverage code, the lines inside the test file seems not covering anything inside the subscribe. I have tried using MockBackend in mocking the api call inside a function on service but I'm not sure how to do the mocking on a subscribed object, can somebody please help me?
The below is in test.component.ts
subscribeToEvents() {
this.subscription = this.czData.$selectedColorZone
.subscribe(items => {
this.resourceLoading = true;
if (!this.resourceData || (this.resourceData && this.resourceData.length === 0)) {
this.settings.layout.flypanel.display = false;
this.getAllResources(this.pagination.start, this.pagination.size);
}
else {
this.pagination.start = 1;
this.pagination.end = this.pagination.size;
this.getAllResources(1, this.pagination.size);
this.settings.layout.flypanel.display = true;
}
});
return true;
}
The screenshot of the coverage code
You can't do this, as the subscription is resolved asynchronously. So the synchronous test completes before the async task is resolved.
If all you want is coverage, you can just make the test async. This will cause the Angular test zone to wait until the async task is resolved, before completing the test
import { async } from '#angular/core/testing';
it('..', async(() => {
component.subscribeToEvents();
}))
You can't try to expect anything here, as there is no callback hook for when the task is resolved. So this is really a pointless test. It will give you coverage, but you aren't actually testing anything. For instance, you might want to test that the variables are set when the subscription is resolved.
Based on the code provided, what I would do instead is just mock the service, and make it synchronous. How can you do that? We you can make the mock something like
class CzDataSub {
items: any = [];
$selectedColorZone = {
subscribe: (callback: Function) => {
callback(this.items);
}
}
}
Then just configure it in the test
let czData: CzDataStub;
beforeEach(() => {
czData = new CzDataStub();
TestBed.configureTestingModule({
providers: [
{ provide: CzData, useValue: czData }
]
})
})
Now in your tests, you don't need to make it async, and you can provide any value you want by just setting the items property on the mock, and subscriber will get it
it('..', () => {
czData.items = something;
component.subscribeToEvents();
expect(component.settings.layout.flypanel.display).toBe(false);
})
UPDATE
I think I was half asleep when I wrote this post. One of the above statements is incorrect
You can't try to expect anything here, as there is no callback hook for when the task is resolved.
This is not completely true. This is what fixture.whenStable() is for. For instance if this is your service
class CzData {
_value = new Subject<>();
$selectedColorZone = this._value.asObservable();
setValue(value) {
this._value.next(value);
}
}
Then this is how you would make the test work
let czData: CzData;
let fixture: ComponentFixture<YourComponent>;
let component: YourComponent;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ CzData ],
declarations: [ YourComponent ]
});
fixture = TestBed.createComponent(YourComponent);
component = fixture.componentInstance;
czData = TestBed.get(czData);
})
it('..', async(() => {
component.subscribeToEvents();
czData.setValue(somevalue);
fixture.whenStable().then(() => {
expect(component.settings.layout.flypanel.display).toBe(false);
})
}))
We use fixture.whenStable() to to wait for the async tasks to complete.
This is not to say that using the mock is wrong. A lot of the time, using the mock would be the way to go. I just wanted to correct my statement, and show how it could be done.
Consider how Angular Outputs are tested since they are subscribed to during testing: https://angular.io/guide/testing#clicking
it('should raise selected event when clicked (triggerEventHandler)', () => {
let selected: Hero;
comp.selected.subscribe((hero: Hero) => selectedHero = hero);
heroDe.triggerEventHandler('click', null);
expect(selectedHero).toBe(expectedHero);
});
So try:
const expectedItem = {}; // mock the expected result from 'subscribeToEvents'
it('should raise selected event when clicked (triggerEventHandler)', () => {
let selectedItem: any; // change to expected type
component.subscribeToEvents.subscribe((item: any) => selectedItem = item);
// fixture.detectChanges(); // trigger change detection if necessary here, depending on what triggers 'subscribeToEvents'
expect(selectedItem).toBe(expectedItem);
});

TDD: Sinon 2.x and trying to test a sync method that uses async

So I've run into another snag, which I'm fighting with... I have a method that is a sync call, and within this method it calls a promise, async, method.
in my app I have the following:
export class App {
constructor(menuService) {
_menuService = menuService;
this.message = "init";
}
configureRouter(config, router) {
console.log('calling configureRouter');
_menuService.getById(1).then(menuItem => {
console.log('within then');
console.log(`configureRouter ${JSON.stringify(menuItem, null, 2)}`);
const collection = menuItem.links.map(convertToRouteCollection);
console.log(`collection ${JSON.stringify(collection, null, 2)}`);
//I think there is an issue with asyn to synch for the test
config.map(collection);
}).catch(err => {
console.error(err);
});
console.log('calling configureRouter assign router');
this.router = router;
}
}
The test I've tried the following within mocha
...
it('should update router config', function () {
const expectedData = {
name: "main menu",
links: [{
url: '/one/two',
name: 'link name',
title: 'link title'
}]
};
const configMapStub = sinon.stub();
const config = {
map: configMapStub
};
const routerMock = sinon.stub();
let app = null;
const actualRouter = null;
let menuService = null;
setTimeout(() => {
menuService = {
getById: sinon.stub().returns(Promise.resolve(expectedData).delay(1))
};
app = new App(menuService);
app.configureRouter(config, routerMock);
}, 10);
clock.tick(30);
expect(app.router).to.equal(routerMock);
expect(menuService.getById.calledWith(1)).to.equal(true);
//console.log(configMapStub.args);
expect(configMapStub.called).to.equal(true);
const linkItem = expectedData.links[0];
const actual = [{
route: ['', 'welcome'],
name: linkItem.name,
moduleId: linkItem.name,
nav: true,
title: linkItem.title
}];
console.log(`actual ${JSON.stringify(actual, null, 2)}`);
expect(config.map.calledWith(actual)).to.equal(true);
});
...
No matter what, I get configMockStub to always get false, while I am getting the menuService.getById.calledWith(1).to.equal(true) to equal true.
The test above was an attempt to try and get 'time' to pass. I've tried it without and have equally failed.
I'm really striking out on ideas on how to test this. Maybe I have the code wrong to reference a promise inside this method.
The only thing I can say I don't have any choice over the configureRouter method. Any guidance is appreciated.
Thanks!
Kelly
Short answer:
I recently discovered I was trying to make configureRouter method be a synchronous call (making it use async await keywords). What I found out was Aurelia does allow that method to be promised. Because of this, the test in question is no longer an issue.
Longer answer:
The other part of this is that I had a slew of babel issues lining up between babelling for mocha, and then babelling for wallaby.js. For some reason these two were not playing well together.
in the test above, another thing was to also change the following:
it('should update router config', function () {
to
it('should update router config', async function () {
I feel like there was another step, but at this time I cannot recall. In either case, knowing that I could use a promise made my world much easier for Aurelia.

How should I write a unit test forJavaScript frontend service that uses promise with Karma, Mocha and Sinon

Trying to test some services I'm writing that interact with a 3rd party API and wondering how to test it efficiently.
I have the next method:
function getMemberProfile(memberId) {
//Make sure memberId is defined and that it is a number
if (!isNaN(memberId)) {
return Client.authorizedApiRequest('/members/' + memberId).get();
}
return Promise.reject(new Error('Proper memberId was not supplied'));
}
When Client.authorizedApiRequest('/members/' + memberId).get() calls a 3rd party API and returns a Promise that resolves to some Object (i.e. {id:12,name:'John Doe'}).
So, how should I test the getMemberProfile function? I was thinking about mocking out the Client.authorizedApiRequest("some params").get() with sinon but I can't get it working.
Thanks
OK, got it working. First you'll need to install chai. Then, in your spec file:
beforeEach(function () {
fakeMember = {
member: {
id: 10002,
first_name: 'John',
last_name: 'Doe'
}
};
});
it('should get a member\'s profile by memberId', function () {
//mock
sinon.stub(Client, 'authorizedApiRequest').withArgs('/members/' + fakeMember.member.id).returns({
get: function () {
return Promise.resolve(fakeMember);
}
});
return Members.getMemberProfile(fakeMember.member.id).then(function (response) {
expect(response).to.have.property('member');
expect(response.member).to.have.property('id', fakeMember.member.id);
expect(response.member).to.have.property('first_name', fakeMember.member.first_name);
expect(response.member).to.have.property('last_name', fakeMember.member.last_name);
});
});

How to properly unit test jQuery's .ajax() promises using Jasmine and/or Sinon?

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');