I have an angular app which I'm trying to create a unit test for. There is a particular method which creates an observable through a complex chain of observable pipes and promises, and then subsequently subscribes to that created observable. I need to verify a method call is made inside of the subscription.
The structure is something like this:
private obs$: Observable<T>;
private subscription: Subscription;
methodToTest(): void {
this.obs$ = from( ... ).then( /* chain of piped observables */ );
this.subscription = this.obs$
.subscribe(
(value: T) => {
// bunch of logic
methodToBeVerifiedWasCalled();
},
(error) => { /* error handling */ }
);
}
My test so far looks like this:
it('the method to be verified is successfully called', () => {
// various mocking set up
this.mockApi.methodThatReturnsAPromise.and.returnvalue(Promise.resolve());
// etc.
this.classUnderTest.methodToTest();
// assertions for the various mocks...
expect(this.mockApi.methodThatReturnsAPromise).toHaveBeenCalled();
// etc.
// assert that the target method was called:
expect(this.classUnderTest.methodToBeVerifiedWasCalled).toHaveBeenCalled();
expect(this.classUnderTest.subscription).toBeDefined();
});
Of course, this test fails at the last assertions because the methodToBeVerifiedWasCalled is actually called inside of the subscribe block and the test is just running through it synchronously. (Here, classUnderTest is a spy object with methodToBeVerifiedWasCalled being spied on.)
Since the method assigns a value to the observable, I can't just subscribe to classUndertest.obs$ in an async test and verify the value returned (which doesn't really help solve the problem anyway.) How can I verify that the method inside of the subscribe block is called?
I cannot change the source code, just the tests.
You need to make your test asynchronous and then await something after methodToTest(). If methodToTest() does not return a promise or observable that you could await then you will be forced to just await until some time has passed. Something like:
function delay(ms) {
return new Promise(resolve => setTimeout(ms, resolve));
}
it('the method to be verified is successfully called', async (done) => {
// various mocking set up
this.mockApi.methodThatReturnsAPromise.and.returnvalue(Promise.resolve());
// etc.
const result = this.classUnderTest.methodToTest();
// if the method actually returned a promise you could: await result;
// since it does not, you can just await time:
await delay(1000); // wait 1 second
// assertions for the various mocks...
expect(this.mockApi.methodThatReturnsAPromise).toHaveBeenCalled();
// etc.
// assert that the target method was called:
expect(this.classUnderTest.methodToBeVerifiedWasCalled).toHaveBeenCalled();
expect(this.classUnderTest.subscription).toBeDefined();
// tell jasmine test is done
done();
});
Related
I am using node-mssql to perform a bunch of operations inside a transaction. Spcifically, the methods used in order are begin tran,bulk,bulk,exec,commit. Here is part of the code, which works as-is:
// bulk insert #columns
.then(result => {
const request=new sql.Request(globalTx);
const tableObj = new sql.Table('#columns'); //#columns
/* some code to build the desired tableObj*/
return request.bulk(tableObj);
})
// exec proc
.then(result => {
returnObject.lastSuccessfulStep='bulk #data'
returnObject["#columns rows"]=result.rowsAffected
const request=new sql.Request(globalTx);
/* some code to build the desired proc call*/
return request.execute('spc_sqlDMLCommand');
})
//commit
.then(result => {
return globalTx.commit(); // returns promise
})
So you can see the use of chained .thens. There is a catch() at the end.
My problem is that in the case od the execute method, while the then() /catch() promise structure feels friendly to me, I also need the information that the method returns through its callback. Citing the reference about the second parameter:
callback(err, recordsets, returnValue) - A callback which is called
after execution has completed, or an error has occurred. returnValue
is also accessible as property of recordsets. Optional. If omitted,
returns Promise.
Is there a way to save err/recordsets/returnValue to some objects while still maintaining the flow, ie not having to cut-paste the subsequent then()s into the execute's callback?
Solution attempt:
I tried using a callback:
request.execute('spc_sqlDMLCommand',function cb(a,b,c){
console.log('a:\n',a,'b:\n',b,'c:\n',c)
});
but I got this error:
message: 'Requests can only be made in the LoggedIn state, not the
SentClientRequest state', code: 'EINVALIDSTATE'
Following this up on the internet, it seems to be about multiple queries having problems. But, it already works for me if I do it in the way mentioned above...
I have the following endpoint in a class called UserApi.js:
const controller = 'User';
...
export async function getEmployeeInfo(employeeId)
{
const query = createQueryFromObject({employeId});
const response = await get(`/${controller}/EmployeeInfo?${query}`);
return retrieveResponseData(response, []);
}
This is going to get the required information from an action method in the backend of UserController.cs.
Now, say that I want to display this information in EmployeeView.vue class, do I have to await it again? Why or why not? Initially, I would say no, you don't, as you already dealt with the await/async in the UserApi.js class, but what about the Promise.resolve? Please explain.
methods: {
async setReportData(
employeeId
) {
this.isBusy = true;
Promise.resolve(getEmployeeInfo(
employeeId
)).then((resultsEmployeeInfo) => {
this.reportDatatableEmployeeInfo = resultsEmployeeInfo;
})
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
})
.finally(() => {
this.isBusy = false;
});
},
Update:
....
* #param {Object} response
* #param {any} defaultData
* #param {Function} predicate
* #returns {Promise}
*/
export function retrieveResponseData(response, defaultData = null, predicate = (predicateResponse) => predicateResponse) {
const data = predicate(response) ? response.data : null;
return data || defaultData;
}
You need to await it since a function declared with async keyword ALWAYS returns a Promise, even if you do only synchronous stuff inside of it, hence you need to await or "thenize" it to access the value it resolved to. That depends from the implementation details of async functions which are just generators that yield promises.
If this concerns you because you work inside sync modules and don't like to use async functions just to execute more async functions, there's a good news, TOP-LEVEL await MODULES proposal is at stage 4, so it'll very soon be shipped with the next ECMA version. This way you will be able to await inside modules as if they were wrapped by async functions !
https://github.com/tc39/proposal-top-level-await
I can't tell if you need to await it again, because I can't tell what retrieveResponseData does. It might take the resolved value and wrap it in a fresh promise, which would then require callers of getEmployeeInfo to await the result.
Here's the why:
A Promise is a wrapper around a value
await unwraps a Promise. So does the .then() handler you can register with a Promise (but the value is only unwrapped within the function you provide to .then()).
Just like a gift in the real world, once something has been unwrapped, you don't need to unwrap it again. However, very conveniently for us, you can still use await on a value that is not wrapped in a Promise, and it will just give you the value.
You can wrap any value in a Promise, like so:
let wrappedFive = Promise.resolve(5)
//> wrappedFive is a Promise that must be unwrapped to access the 5 inside it
// this does _exactly_ the same thing as the above
let wrappedFive = new Promise(resolve => {
resolve(5)
})
Sometimes you end up in a situation where you can't use await, because you're in a function that cannot be marked async. The lifecycle methods of front-end frameworks like React (and possibly Vue) are like that: the framework needs each lifecycle method to do its job and be done immediately. If you mark the lifecycle method as async, you can often prevent it from having the intended effect.
In cases like that, you often need to use chained .then() handlers, which is a little uglier, but it works:
componentDidMount() {
// this API call is triggered immediately by lifecycle,
// but it basically starts a separate thread -- the rest
// of this function does not wait for the call to finish
API.getUserInfo()
.then(userInfo => {
// this happens after the API call finishes, but
// componentDidMount has already finished, so what happens
// in here cannot affect that function
this.setState({ username: userInfo.username })
})
// this happens immediately after the API call is triggered,
// even if the call takes 30 seconds
return 5
}
Note that a Promise does not actually start a separate thread -- these all happen in the same thread that executes the lifecycle method, i.e. the browser's renderer thread. But if you think of the codepath that executes, a Promise that you don't wait for basically introduces a fork into that codepath: one path is followed immediately, and the other path is followed whenever the Promise resolves. Since browserland is pretty much a single-threaded context, it doesn't really hurt you to think of creating a Promise as spawning a separate thread. This is a nuance you can ignore until you are comfortable with asychronous patterns in JS.
Update: retrieveResponseData does not appear to return a Promise. I could be wrong, if predict returns a Promise, but if that were true, then the ternary would always return response.data because unwrapped Promises are truthy values (even Promise.resolve(false) is truthy).
However, anyone who calls getEmployeeInfo will have to wait it, because that function is marked async, and that means it returns a Promise even if nothing inside it is is asynchronous. Consider this extreme example:
// this function returns a number
function gimmeFive() {
return 5
}
// this function returns a Promise wrapped around a number
async function gimmeFive() {
return 5
}
Async function getEmployeeInfo awaits the result of the get call in order to return the value returned by a call to retrieveResponeData.
Assuming neither get nor retrieveResponeData errors, the value syntactically returned in the body of getEmployeeInfo is used to resolve the promise object actually returned by calling getEmployeeInfo.
Promise.resolve is not needed to convert the result of calling getEmployeeInfo into a promise because, given async functions return promises, it already is.
It doesn't matter if retrieveResponseData returns a promise or not: standard async function processing waits for a returned promise value to be settled before resolving the promise returned when calling the async function.
Async function setRreportData is declared as async but written using chained promise handler methods to process data and error conditions in order - the async declaration could be omitted, but may be useful if modifications are made.
The results can only be used to update the page at a time when the data has finished being obtained and extracted, shown as a comment in the following code:
setReportData( employeeId) {
this.isBusy = true;
getEmployeeInfo(
employeeId
).then((resultsEmployeeInfo) => {
this.reportDatatableEmployeeInfo = resultsEmployeeInfo;
// At this point the result in this.reportDatatableEmployeeInfo can be used to update the page
})
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
})
.finally(() => {
this.isBusy = false;
});
},
Displaying the data using EmployeeView.vue class must wait until the data is available. The simplest place to insert updating the page (in the posted code) is within the then handler function inside setReportData.
Displaying the data
As posted setReportData does not notify its caller of when report data is available, either by means of a callback or by returning a promise. All it does is save the result in this.reportDatatableEmployeeInfo and dies.
Without using callbacks, setReportData is left with two choices
Return a promise that is fulfilled with, say,resultsEmployeeInfo or undefined if an error occurs:
setReportData( employeeId) {
this.isBusy = true;
// return the final promise:
return getEmployeeInfo(
employeeId
)
.then( (resultsEmployeeInfo) =>
(this.reportDatatableEmployeeInfo = resultsEmployeeInfo)
)
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
// return undefined
})
.finally(() => {
this.isBusy = false;
});
},
which could be used in a calling sequence using promises similar to
if(!this.busy) {
this.setReportData(someId).then( data => {
if( data) {
// update page
}
}
If you wanted to make the call in an async context you could use await:
if(!this.busy) {
const data = await this.setReportData(someId);
if( data) {
// update page
}
}
Update the page from within setReportData after the data becomes available ( as shown as a comment in the first part of this answer). The method should probably be renamed from setReportData to getReportData or similar to reflect its purpose.
I have an Angular service with a method that makes a call to a method in an httpService that returns an observable.
Once that method returns success, a series of parallel calls are made to the same method of the httpService but with different params to save some images.
I am trying to unit test that the calls are made in the right order and with the right params.
When I try to use spies and .toHaveBeenCalled() I only get the 1st call recognised by Jasmine.
I am assuming it has something to do with timing but I am lost and can't find any examples of this use case. Any guidance will be appreciated.
Edit: the code works 100%, my issue is just how to write unit tests for the sequence calls.
// Service
// HttpS is a custom service that makes HTTP requests
constructor(private httpS: HttpS) {
}
save(params: {images: any[]}): Observable<any> {
return this.saveEntry(params);
}
private saveEntry(params: {images: any[]}): Observable<any> {
return this.httpS.put('url', {something: 'something'}).pipe(
// once this request is successful, make parallel requests
concatMap(() => {
const reqs = [];
params.images.forEach(image => {
reqs.push(this.saveImage({ image }));
});
return forkJoin(reqs);
})
);
}
private saveImage(data: {image: any}): Observable<any> {
return this.httpS.put('url',{imgData: data.image});
}
// Test
describe('save', () => {
it('should make a PUT request with something, then on success multiple parallel PUT requests with imgData', () => {
spyOn(httpService, 'put').and.callFake(() => cold('-a|', { a: {} }));
service.save(data).pipe(take(1)).subscribe();
// The mock data I'm passing has 3 images so it results in 1 call first, then 3 in parallel, 1 for each image, but this test says it has been called just once.
expect(httpS.put).toHaveBeenCalledTimes(4);
// Trying .toHaveBeenCalledWith() but obviously that fails too
})
})
I think it has to do with subscribe running at a later point in time than your assertions (due to asynchronous nature).
Try this:
describe('save', () => {
it('should make a PUT request with something, then on success multiple parallel PUT requests with imgData', (done) => { // add done to tell Jasmine when you're done with unit test
spyOn(httpService, 'put').and.callFake(() => cold('-a|', { a: {} }));
service.save(data).pipe(take(1)).subscribe(response => {
expect(httpS.put).toHaveBeenCalledTimes(4); // make assertion once the response comes back
done(); // call done to let Jasmine know you're done with the test
});
})
})
I'm new to Angular and I'm still trying to figure out how things work for it. I'm currently having trouble testing a Component that depends on a Service that returns a Promise. The function I'm testing is structured like the following:
success: boolean;
borrowBook() {
this.bookService.borrow(this.selectedBook.id)
.then(() => {
this.success = true;
})
.catch((error: BorrowingError) => {
this.success = false;
});
}
Now, I'm not really sure if something like the code above is considered idiomatic, but that's how I wrote the code. In my unit test, I mocked the bookService using jasmine.createSpyObj function, and defined the stub as follows:
mockBookService.borrow.and.returnValue(Promise.resolve(testResult));
However, my test fails saying that component.success is undefined when I expected it to be truthy. My test is programmed as follows:
it('test', async(() => {
mockBookService.borrow.and.returnValue(Promise.resolve(testResult));
//setup pre-conditions here...
component.borrowBook(();
expect(component.success).toBeTruthy();
}));
My impression is that the expectations are being checked even before the Promise is handled accordingly.
I chanced upon this article about testing asynchronous code in Angular, which stated that the flushMicrotasks function can be used to run all the async components before checking expectations. This is only provided through the fake zone created through the fakeAsync, so in my code I used that instead of just async.
it('test', fakeAsync(() => { //used fakeAsync instead
mockBookService.borrow.and.returnValue(Promise.resolve(testResult));
//setup pre-conditions here...
component.borrowBook(();
flushMicrotasks(); //added this before checking expectations
expect(component.success).toBeTruthy();
}));
So basically I have the function I want to test which we will call function A. I want to test if function B was called inside function A. The problem is that function B is being called inside function A asynchronously through a resolved Promise. This will cause sinon assertion to fail because the test will finish before function B is called!
Here is a working code scenario.
const sinon = require('sinon');
describe('functionToBeTested', () => {
it('someFunction is called', () => {
// spy on the function we need to check if called
const spy = sinon.spy(someClass.prototype, 'someFunction');
// call the function being tested
functionToBeTested();
// check if spy was called
sinon.assert.called(spy);
});
});
class someClass {
someFunction() {
console.log('Hello');
}
}
const somePromise = Promise.resolve();
function functionToBeTested() {
const instance = new someClass();
// some synchronous code here
// if instance.someFunction() is called here the test will pass
// .
// .
// .
somePromise.then(() => {
instance.someFunction();
// the function is called and Hello is printed but the test will fail
})
// .
// .
// .
// some synchronous code here
// if instance.someFunction() is called here the test will pass
}
Your example is a bit unconventional. You have functionToBeTested which has a dual behavior (sync and async at the same time). When you put this method under test, the behavior should be well known and standardized before hand, so that you can structure the test and assertions accordingly.
The problem in this scenario is that you try to validate the behavior of the function is sync mode, although the inner parts work in a fire-and-forget fashion - i.e. there is no dependency on the result of the instance.someFunction() method.
If functionToBeTested() returned a promise - thus being async by design, that would be straightforward for your test scenario. But in this case, you will need an unconventional testing approach as well. That means that if you do something like:
describe('functionToBeTested', () => {
it('someFunction is called', (done) => {
// spy on the function we need to check if called
const spy = sinon.spy(SomeClass.prototype, 'someFunction');
// call the function being tested
functionToBeTested();
setTimeout(() => {
// check if spy was called
sinon.assert.called(spy);
done();
}, 10);
});
});
the test would pass. What happened here is that we declared the test async by making use of the done argument in the callback. Also, we added a timer to mimic a delay prior to checking whether the spy was called.
Since the 'fire-and-forget' call only prints out a message, waiting for 10ms would be enough. If the promise took longer to complete, the wait time should be adjusted.
As stated before, unconventional implementations require unconventional approaches. I would suggest that you reconsider your requirements and redesign the solution.