I'm trying to test a component that loads data asynchronously when mounted. The component works as expected, it's just the test that's giving me issues. The component's async loadData() function hangs at await axios.get() while jest test runner is in the component.vm.$nextTick(). As a result, the checks in the $nextTick loop never pass.
Immediately after the $nextTick loop times out, the component's await statement completes and the component renders itself. axios is mocked, so it should resolve really fast. If I remove the await and just fill in a constant instead, the entire thing executes as expected.
I'm guessing that $nextTick loop is not asynchronous and it's consuming the thread, even though this is the recommended way of testing asynchronous stuff. The problem is, I don't have an onclick async handler to await: this method is called from onMount.
Unfortunately, I don't know how to make a jsFiddle of this one, so I hope this will be enough:
my component (the relevant parts)
export default {
data() { return { content: '' }; },
mounted() { this.loadDoc() }
methods: {
async loadDoc() {
const res = await axios.get('some url'); // <-- this is the line that hangs until timeout
// const res = { data: 'test data'}; // this would test just fine
this.content = res.data;
}
}
}
and my component.spec.js:
jest.mock('axios', () => ({
get: async (url) => {
return { data: 'test data' };
}
};
describe('my super test', () => {
it('renders', (done) => {
const doc = shallowMount(myComponent);
doc.vm.$nextTick(() => {
expect(doc.html()).toContain('test data'); // <-- this never matches
done();
});
});
});
I would delete, but I just spent quite some hours for something that was suggested in the docs, but not explained that it's the only way... I'm hoping somebody else finds this useful.
Using flush-promises package instead of $nextTick loop immediately "fixed" the problem
Code sample (rework of above):
describe('my super test', () => {
it('renders', async() => {
const doc = shallowMount(myComponent);
await flushPromises();
expect(doc.html()).toContain('test data'); // <-- now it works
});
});
Related
Every test I made with jest it's begining with await app.transaction(async(trx) => { and ends with ..rollback..
await app.transaction(async(trx) => {
const a = await update();
expect(a).toBe(something);
await trx.rollback();
});
The actual test is:
const a = await update();
expect(a).toBe(something);
And I want instead of write this wrapper for every test function, just to write within the beforeEach and afterEach.
Since the test is inside of the parameter to transaction you can't really do this in a beforeEach since it will differ based on the test. However you can avoid duplicating the code in each test by writing a helper function like this:
async function wrapper(testFn) {
return app.transaction(async(trx) => {
await testFn();
return trx.rollback();
}
}
// then do this in each test:
it('should work', () => {
await wrapper(async () => {
const a = await update();
expect(a).toBe(something);
});
});
The problem I'm having is that Jest is reporting setResultsSpy is being called 0 times when in fact, I know that it is being called. I know this by putting console.log(results) under the const results = await getFileList(data.path); in my code and was able to see results returned.
My guess right now is that try-catch blocks creates a local scope, which is causing those calls to not be registered. If this is true, my question is "how can I test if those methods have been called"?
// test_myFunction.js
test((`myFunction with valid path should return list of files`), () => {
const actions = {
setMsg: () => { },
setButton: () => {},
setResults: () => {},
setAppState: () => {}
};
const setMsgSpy = jest.spyOn(actions, 'setMsg');
const setSubmitButtonStateSpy = jest.spyOn(actions, 'setButton');
const setResultsSpy = jest.spyOn(actions, 'setResults');
const setAppStateSpy = jest.spyOn(actions, 'setAppState');
const returnedFileList = [
'file1.pdf',
'file2.pdf',
'file3.pdf',
];
const requestConfig = {
component: COMPONENTS.myComponent,
request: RequestTypes.REQUEST,
data: {path: 'folder1'},
actions
};
processRequest(requestConfig)
expect(setMsgSpy).toHaveBeenCalledTimes(1);
expect(setMsgSpy)
.toHaveBeenCalledWith('loading');
expect(setButtonSpy).toHaveBeenCalledTimes(1);
expect(setResultsSpy).toHaveBeenCalledTimes(1);
expect(setResultsSpy).toHaveBeenCalledWith(returnedFileList);
expect(setAppStateSpy).toHaveBeenCalledTimes(1);
expect(setAppStateSpy).toHaveBeenCalledWith('confirm');
});
_
// myFunction.js
async function processRequest({
component,
request,
data,
actions,
}){
if (component === COMPONENTS.myComponent) {
const path = data.path.trim();
switch (request) {
case RequestTypes.REQUEST:
actions.setMsg('message');
actions.setButton('disabled');
try {
const results = await getFileList(data.path);
actions.setResults(results);
actions.setAppState('confirm');
} catch (e) {
actions.setError(e);
actions.setAppState('error');
}
}
break;
default:
break;
}
}
The the problem was Jest was failing out of the test before the results from getFileList() execution has completed since getFileList() is an async function.
The solution is for the test to handle the execution asynchronously as per the documentation. There are 4 ways to solve this problem:
Use callbacks
Use .then() and .catch() on the returned promise (see docs on .then() here and .catch() here)
Use .resolves() or .rejects() Jest methods on expect() to let Jest resolve the promise.
Use Async-Await syntax by declaring the test anonymous function as async and using await on processRequest() .
I went with option 4 as I enjoy using async-await syntax. Here's the solution:
// test_myFunction.js
test((`myFunction with valid path should return list of files`), async () => {
//(all of the variables established from above)
await processRequest(requestConfig)
expect(setMsgSpy).toHaveBeenCalledTimes(1);
expect(setMsgSpy)
.toHaveBeenCalledWith('loading');
expect(setButtonSpy).toHaveBeenCalledTimes(1);
expect(setResultsSpy).toHaveBeenCalledTimes(1);
expect(setResultsSpy).toHaveBeenCalledWith(returnedFileList);
expect(setAppStateSpy).toHaveBeenCalledTimes(1);
expect(setAppStateSpy).toHaveBeenCalledWith('confirm');
});
Notice async being used on the first line and await when calling processRequest(requestConfig).
I am fairly new to NestJS + Typescript + RxJs tech stack. I am trying to write a unit test case using Jest for one of my functions but not sure if doing it correctly.
component.service.ts
public fetchComponents(queryParams) {
const url = this.prepareUrl(queryParams);
const data$ = this.httpService.get(url);
return data$
.pipe(map(({ data }) => data));
}
component.sevice.spec.ts
Test case works and passes
describe('fetchComponents', () => {
const query = {
limit: 10,
offset: 0
};
const result: AxiosResponse = {
data: 'Components',
status: 200,
statusText: 'OK',
headers: {},
config: {}
};
it('should return Dummy Data when called successfully', () => {
componentService.prepareUrl = jest.fn();
jest.spyOn(httpService, 'get').mockImplementation(() => of(result));
componentService.fetchComponents(market, query)
.subscribe(
(res) => {
expect(res).toEqual('Components');
}
);
});
});
Can you please provide suggestions and pointers on how exactly I should test this function. Also without using Library like marbel-rx
I am not sure if I am testing it correctly. Is there something else also which I should test?
Since Observables are asynchronous, you have to add the asynchronous done paramter and call done() after the expect that is executed last. Otherwise, jest will finish the test run after subscribe() is called without waiting for the execution of the asynchronous execution of subscribe's callback. Try to make your test fail by for example by expecting 'Komponents'. The test will not fail.
Also, I'd recommend to use mockImplementationOnce instead of mockImplementation when possible, to avoid implicitly reusing mock behaviors in later calls and therewith creating implicit dependencies.
it('should return Dummy Data when called successfully', done => {
// Add done parameter ^^^^
componentService.prepareUrl = jest.fn();
jest.spyOn(httpService, 'get').mockImplementationOnce(() => of(result));
// Prefer mockImplementationOnce ^^^^
componentService.fetchComponents(market, query)
.subscribe(
(res) => {
expect(res).toEqual('Components');
done();
// ^^^^^^ Call done() when test is finished
}
);
});
I use Supertest to test my Express apps, but I'm running into a challenge when I want my handlers to do asynchronous processing after a request is sent. Take this code, for example:
const request = require('supertest');
const express = require('express');
const app = express();
app.get('/user', async (req, res) => {
res.status(200).json({ success: true });
await someAsyncTaskThatHappensAfterTheResponse();
});
describe('A Simple Test', () => {
it('should get a valid response', () => {
return request(app)
.get('/user')
.expect(200)
.then(response => {
// Test stuff here.
});
});
});
If the someAsyncTaskThatHappensAfterTheResponse() call throws an error, then the test here is subject to a race condition where it may or may not failed based on that error. Even aside from error handling, it's also difficult to check for side effects if they happen after the response is set. Imagine that you wanted to trigger database updates after sending a response. You wouldn't be able to tell from your test when you should expect that the updates have completely. Is there any way to use Supertest to wait until the handler function has finished executing?
This can not be done easily because supertest acts like a client and you do not have access to the actual req/res objects in express (see https://stackoverflow.com/a/26811414/387094).
As a complete hacky workaround, here is what worked for me.
Create a file which house a callback/promise. For instance, my file test-hack.js looks like so:
let callback = null
export const callbackPromise = () => new Promise((resolve) => {
callback = resolve
})
export default function callWhenComplete () {
if (callback) callback('hack complete')
}
When all processing is complete, call the callback callWhenComplete function. For instance, my middleware looks like so.
import callWhenComplete from './test-hack'
export default function middlewareIpnMyo () {
return async function route (req, res, next) {
res.status(200)
res.send()
// async logic logic
callWhenComplete()
}
}
And finally in your test, await for the callbackPromise like so:
import { callbackPromise } from 'test-hack'
describe('POST /someHack', () => {
it.only('should handle a post request', async () => {
const response = await request
.post('/someHack')
.send({soMuch: 'hackery'})
.expect(200)
const result = await callbackPromise()
// anything below this is executed after callWhenComplete() is
// executed from the route
})
})
Inspired by #travis-stevens, here is a slightly different solution that uses setInterval so you can be sure the promise is set up before you make your supertest call. This also allows tracking requests by id in case you want to use the library for many tests without collisions.
const backgroundResult = {};
export function backgroundListener(id, ms = 1000) {
backgroundResult[id] = false;
return new Promise(resolve => {
// set up interval
const interval = setInterval(isComplete, ms);
// completion logic
function isComplete() {
if (false !== backgroundResult[id]) {
resolve(backgroundResult[id]);
delete backgroundResult[id];
clearInterval(interval);
}
}
});
}
export function backgroundComplete(id, result = true) {
if (id in backgroundResult) {
backgroundResult[id] = result;
}
}
Make a call to get the listener promise BEFORE your supertest.request() call (in this case, using agent).
it('should respond with a 200 but background error for failed async', async function() {
const agent = supertest.agent(app);
const trackingId = 'jds934894d34kdkd';
const bgListener = background.backgroundListener(trackingId);
// post something but include tracking id
await agent
.post('/v1/user')
.field('testTrackingId', trackingId)
.field('name', 'Bob Smith')
.expect(200);
// execute the promise which waits for the completion function to run
const backgroundError = await bgListener;
// should have received an error
assert.equal(backgroundError instanceof Error, true);
});
Your controller should expect the tracking id and pass it to the complete function at the end of controller backgrounded processing. Passing an error as the second value is one way to check the result later, but you can just pass false or whatever you like.
// if background task(s) were successful, promise in test will return true
backgroundComplete(testTrackingId);
// if not successful, promise in test will return this error object
backgroundComplete(testTrackingId, new Error('Failed'));
If anyone has any comments or improvements, that would be appreciated :)
// Balance.jsx
...
updateToken () {
const parseResponse = (response) => {
if (response.ok) {
return response.json()
} else {
throw new Error('Could not retrieve access token.')
}
}
const update = (data) => {
if (data.token) {
this.data.accessTokenData = data
} else {
throw new Error('Invalid response from token api')
}
}
if (this.props.balanceEndpoint !== null) {
return fetch(this.props.accessTokenEndpoint, {
method: 'get',
credentials: 'include'
})
.then(parseResponse)
.then(update)
.catch((err) => Promise.reject(err))
}
}
componentDidMount () {
this.updateToken()
.then(() => this.updateBalance())
}
}
// Test
it('updates the balance', () => {
subject = mount(<Balance {...props} />)
expect(fetchMock.called('balance.json')).to.be.true
})
I can't figure out how to test the above using Mocha. The code is does work the method updateBalance is called and the fetch api call actually does happen, but the test still fails. If I call updateBalance() synchronously it passes... How do I tell the test to wait for the promise to resolve?
You don't really say what you want to test that the
method does, but if all you want to test is that the method resolves on a network call, then there is no need for Sinon or any of that, as this is all you need:
describe("BalanceComponent", () => {
it("should resolve the promise on a successful network call", () => {
const component = new BalanceComponent({any: 'props', foo: 'bar'});
// assumes you call a network service that returns a
// successful response of course ...
return component.updateToken();
});
});
This will test that the method actually works, but it is slow and is not a true unit test, as it relies on the network being there and that you run the tests in a browser that can supply you with a working implementation of fetch. It will fail as soon as you run it in Node or if the service is down.
If you want to test that the method actually does something specific, then you would need to to that in a function passed to then in your test:
it("should change the token on a successful network call", () => {
const component = new BalanceComponent({any: 'props', foo: 'bar'});
const oldToken = component.data.accessTokenData;
return component.updateToken().then( ()=> {
assert(oldToken !== component.data.accessTokenData);
});
});
If you want to learn how to test code like this without being reliant on there being a functioning link to the networked service you are calling, you can check out the three different techniques described in this answer.