Here is a test stub that I have written in Mocha/Chai. I can easily dispatch an action and assert that the state is equal to what I expect, but how do I validate that it followed the expected process along the way (IE the earlier tests)?
/**
* This test describes the INITIALIZE_STATE action.
* The action is asynchronous using the async/await pattern to query
* The database. The action creator returns a thunk which should in turn
* return the new state with a list of tables and their relationships with eachother
**/
describe('Initialize state', () => {
it('Should check if state is empty', () => {});
it('Should check if tables/relationships exist', () => {});
it('Should check if new tables have been added', () => {});
it('Should merge new and existing tables and relationships', () => {
// Here is where we would dispatch the INITIALIZE_STATE
// action and assert that the new state is what I expect it to be.
});
});
I haven't written any code for the actual action itself yet, because I want the code to pass these validations. Some psuedo-code might look like this
export function initializeState() {
return function(dispatch) {
let empty = store.getState().empty
let state = (empty) ? await getLastPersistedState() : store.getState()
let tables = state.tables;
let payload = tables.concat(await getNewTables(tables));
dispatch({type: 'INITIALIZE_STATE', payload});
}
}
function getLastPerisistedState() {
return mongodb.findall(state, (s) => s);
}
function getNewTables(tableFilter) {
return sql.query("select table_name from tables where table_name not in (" + tableFilter + ")");
}
Here is the solution that I came up with. There may be a better one, but so far no one has been able to provide one. I decided to go with a refactored set of actions and a separate store for my testing. The actions are function generators rather than using thunk. They yield out the actions that thunk will dispatch in the production code. In my test I can then dispatch those actions myself and verify that the resulting state is what I'd expect. This is exactly what thunk will do but it allows me to insert myself as the middle man rather than depending on the thunk middle-ware.
This is also super useful because it makes separating the action logic from the dispatch and state logic extremely easy, even when you are testing asynchronous flow.
For the database, I automatically generate a stub and use promises to simulate the async query. Since this project is using sequelize anyways, I just used sequelize to generate the stub.
Here is the code
_actions.js
export function *initializeState() {
//We will yield the current step so that we can test it step by step
//Should check if state is empty
yield {type: 'UPDATE_STEP', payload: 'IS_STATE_EMPTY'};
yield {type: 'UPDATE_STEP_RESULT', payload: stateIsEmpty()};
if(stateIsEmpty()) {
//todo: Implement branch logic if state is empty
}
//...
}
sequelize/_test/generate.js
async function createMockFromSql(db, sql, filename) {
let results = await db.query(sql, {type: db.Sequelize.QueryTypes.SELECT});
return new Promise((resolve, reject) => {
// Trim the results to a reasonable size. Keep it unique each time
// for more rigorous testing
console.log('trimming result set');
while (results.length > 50) {
results.splice(results.length * Math.random() | 0, 1);
}
fs.writeFile(path.resolve(__dirname, '../../sequelize/_test', filename), JSON.stringify(results, null, 2), err => {
if (err) {
console.error(err);
reject(false);
}
resolve(true);
})
})
}
test/actions.js
...
it('Should check if state is empty', () => {
let action = initializeState();
expect(action.next()).to.deep.equal({type: 'UPDATE_STEP', payload: 'IS_STATE_EMPTY'})
});
Related
I'm trying to write tests for the following function that uses retryWhen operator:
// some API I'm using and mocking out in test
import { geoApi } from "api/observable";
export default function retryEpic(actions$) {
return actions$.pipe(
filter(action => action === 'A'),
switchMap(action => {
return of(action).pipe(
mergeMap(() => geoApi.ipLocation$()),
map(data => ({ data })),
retryWhen(errors => {
return errors.pipe(take(2));
}),
);
}),
);
}
The code is supposed to perform a request to some remote API geoApi.ipLocation$(). If it gets an error, it retries 2 times before giving up.
I have written the following test code that uses Jest and RxJS TestScheduler:
function basicTestScheduler() {
return new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
}
const mockApi = jest.fn();
jest.mock('api/observable', () => {
return {
geoApi: {
ipLocation$: (...args) => mockApi(...args),
},
};
});
describe('retryEpic()', () => {
it('retries fetching 2 times before succeeding', () => {
basicTestScheduler().run(({ hot, cold, expectObservable, expectSubscriptions }) => {
const actions$ = hot('-A');
// The first two requests fail, third one succeeds
const stream1 = cold('-#', {}, new Error('Network fail'));
const stream2 = cold('-#', {}, new Error('Network fail'));
const stream3 = cold('-r', { r: 123 });
mockApi.mockImplementationOnce(() => stream1);
mockApi.mockImplementationOnce(() => stream2);
mockApi.mockImplementationOnce(() => stream3);
expectObservable(retryEpic(actions$)).toBe('----S', {
S: { data: 123 },
});
expectSubscriptions(stream1.subscriptions).toBe('-^!');
expectSubscriptions(stream2.subscriptions).toBe('--^!');
expectSubscriptions(stream3.subscriptions).toBe('---^');
});
});
});
This test fails.
However, when I replace retryWhen(...) with simply retry(2), then the test succeeds.
Looks like I don't quite understand how to implement retry with retryWhen. I suspect this take(2) is closing the stream and kind of preventing everything from continuing. But I don't quite understand it.
I actually want to write some additional logic inside retryWhen(), but first I need to understand how to properly implement retry() with retryWhen(). Or perhaps that's actually not possible?
Additional resources
My implementation of retryWhen + take was based on this SO answer:
How to create an RXjs RetryWhen with delay and limit on tries
Official docs:
retryWhen
You can use retryWhen for those two purposes, one to have your logic in it and the second is the retry numbers you'd like to give it (no need to use retry operator):
// some API I'm using and mocking out in test
import { geoApi } from "api/observable";
export default function retryEpic(actions$) {
return actions$.pipe(
filter(action => action === 'A'),
switchMap(action => {
return of(action).pipe(
mergeMap(() => geoApi.ipLocation$()),
map(data => ({ data })),
retryWhen(errors =>
errors.pipe(
mergeMap((error, i) => {
if (i === 2) {
throw Error();
}
// return your condition code
})
)
)
)
}),
);
}
Here is a simple DEMO of that.
As for understanding this logic:
retryWhen and retry operators, according to the Official docs you've referenced:
resubscribing to the source Observable (if no error or complete executes)
This is why you can't pipe retry and retryWhen together. You can say that these operators are a chain breakers...
Is there a way to show why tested function can pass?
When I follow Jest test Async Code section
It says:
Be sure to return the promise - if you omit this return statement,
your test will complete before fetchData completes.
And my code is:
function add1(n) {
return new Promise((res, rej)=>{
res(n+1)
})
}
test('should add 1', function() {
expect.assertions(1)
//////////////////////////// I did not use RETURN here
add1(10).then((n11)=>{
expect(n11).toBe(11)
})
});
This still passed, I want to know how this can pass?
The Promise resolves immediately and synchronously so the then gets called immediately and the expect has run before the test finishes. (then callbacks run immediately if the Promise has already resolved)
If you use setTimeout to keep the Promise from resolving immediately and synchronously then the test fails unless you return the Promise:
function add1(n) {
return new Promise((res, rej) => {
setTimeout(() => { res(n + 1) }, 0); // use setTimeout
})
}
test('should add 1', function () {
expect.assertions(1)
// PASSES only if Promise is returned
return add1(10).then((n11) => {
expect(n11).toBe(11);
})
});
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.
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
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);
});