Currently I am trying to write a test that will perform the following actions:
Load my Component
Wait for return from a service class (HTTP Call + Observable)
Check value returned.
Service class:
getMyInfo(): Observable<any[]> {
return this.httpClient.get(`${this.serviceurl}/all`) as Observable<any[]>;
}
Relevant component code:
ngOnInit() {
this.myService.getMyInfo().subscribe((data: any[]) => {
console.log('I was called')
this.myInfo = data;
})
}
In test class, using this method from Service will not work, the method is called, however the return is never applied in the class (Subscription).
Using a stub works, but this is not what I want, since I want to e2e.
const serviceStub = {
getMyInfo() {
return fakeAsyncResponse([{ id: 1,name: 'myname'}]);
}
};
it("should request names on component initiation.", inject([FrontendComponent, Service], async(component: FrontendComponent, service: Service) => {
let debugElement = fixture.debugElement;
let Service = debugElement.injector.get(Service);
let incrementSpy = spyOn(Service, 'getMyInfo').and.callThrough();
component.ngOnInit();
expect(incrementSpy).toHaveBeenCalled();
await fixture.whenStable();
fixture.detectChanges();
console.log(component.myInfo);
expect(component.myInfo.length).toEqual(1);
}));
To sum up, how can I handle real http calls and then process the expect assertion without stubbing the service classes?
Related
I have below code which invokes a service to get some values, I knowingly shut down the backend part to check if error is being added. I noticed it makes the service call twice, can someone explain me why this is happening?
(async () => {
try {
this.countries = await this.testerSvc.getCountries().toPromise();
} catch (err) {
this.addError("Countries");
}
})();
Service code:
getCountries() {
return this.http.get<[]>(this.baseUrl + "countries");
}
On my app.module.ts I had added my component:
bootstrap: [AppComponent, MyComponent]
On my app.component.html, I had
<app-tester> </app-tester>
It was instantiating my component twice, I removed from the bootstrap and now it's instantiating only once. So service call is only once.
I'm making a request to a 3rd party API via NestJS's built in HttpService. I'm trying to simulate a scenario where the initial call to one of this api's endpoints might return an empty array on the first try. I'd like to use RxJS's retryWhen to hit the api again after a delay of 1 second. I'm currently unable to get the unit test to mock the second response however:
it('Retries view account status if needed', (done) => {
jest.spyOn(httpService, 'post')
.mockReturnValueOnce(of(failView)) // mock gets stuck on returning this value
.mockReturnValueOnce(of(successfulView));
const accountId = '0812081208';
const batchNo = '39cba402-bfa9-424c-b265-1c98204df7ea';
const response =client.viewAccountStatus(accountId, batchNo);
response.subscribe(
data => {
expect(data[0].accountNo)
.toBe('0812081208');
expect(data[0].companyName)
.toBe('Some company name');
done();
},
)
});
My implementation is:
viewAccountStatus(accountId: string, batchNo: string): Observable<any> {
const verificationRequest = new VerificationRequest();
verificationRequest.accountNo = accountId;
verificationRequest.batchNo = batchNo;
this.logger.debug(`Calling 3rd party service with batchNo: ${batchNo}`);
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const response = this.httpService.post(url, verificationRequest, config)
.pipe(
map(res => {
console.log(res.data); // always empty
if (res.status >= 400) {
throw new HttpException(res.statusText, res.status);
}
if (!res.data.length) {
this.logger.debug('Response was empty');
throw new HttpException('Account not found', 404);
}
return res.data;
}),
retryWhen(errors => {
this.logger.debug(`Retrying accountId: ${accountId}`);
// It's entirely possible the first call will return an empty array
// So we retry with a backoff
return errors.pipe(
delayWhen(() => timer(1000)),
take(1),
);
}),
);
return response;
}
When logging from inside the initial map, I can see that the array is always empty. It's as if the second mocked value never happens. Perhaps I also have a solid misunderstanding of how observables work and I should somehow be trying to assert against the SECOND value that gets emitted? Regardless, when the observable retries, we should be seeing that second mocked value, right?
I'm also getting
: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:
On each run... so I'm guessing I'm not calling done() in the right place.
I think the problem is that retryWhen(notifier) will resubscribe to the same source when its notifier emits.
Meaning that if you have
new Observable(s => {
s.next(1);
s.next(2);
s.error(new Error('err!'));
}).pipe(
retryWhen(/* ... */)
)
The callback will be invoked every time the source is re-subscribed. In your example, it will call the logic which is responsible for sending the request, but it won't call the post method again.
The source could be thought of as the Observable's callback: s => { ... }.
What I think you'll have to do is to conditionally choose the source, based on whether the error took place or not.
Maybe you could use mockImplementation:
let hasErr = false;
jest.spyOn(httpService, 'post')
.mockImplementation(
() => hasErr ? of(successView) : (hasErr = true, of(failView))
)
Edit
I think the above does not do anything different, where's what I think mockImplementation should look like:
let err = false;
mockImplementation(
() => new Observable(s => {
if (err) {
s.next(success)
}
else {
err = true;
s.next(fail)
}
})
)
I am not using redux-thunk. this keeps error-ing and I am not sure how to fix it. The examples I see online use redux-thunk which I am not using
my repo is here and the file I am trying to test is in tests\actions\...
My action that is being called in the test
import axios from "axios";
var CancelToken = axios.CancelToken;
let fetch_cancel;
export const FETCH_CATEGORIES = "fetch_categories";
export async function fetchCategories() {
fetch_cancel && fetch_cancel();
const request = await axios.get(
`https://d1i9eedhsgvpdh.cloudfront.net/production-plentific-static/api-cache/find-a-pro/api/v1/categories/all.json`,
{
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
fetch_cancel = c;
})
}
);
return {
type: FETCH_CATEGORIES,
payload: request
};
}
The error message means that your actions must be plain objects. For example:
store.dispatch({
type: 'ADD_TODO',
text: 'Understand the middleware'
})
If you make an async request, you can't just return an object, because you need to wait for the request to finish. If you return too early, you return a Promise.
However, I cannot reproduce your error in your sandbox.
I'm making a service that has multiple methods triggering HTTP calls. I want to reveal an Observable to clients that tells them whether there are any pending calls.
I think this approach would generally would work, but feels hacky and not very RxJs-like. I've searched all over StackOverflow and through the RxJs docs, but can't find a better way. Any advice? Thanks!
let pendingCountSubject = new BehaviorSubject<number>(0);
let pendingCount$ = pendingCountSubject.asObservable();
let pendingSubject = new Subject<Observable<any>>();
pendingSubject.pipe(
finalize(() => {
pendingCountSubject.next(pendingCountSubject.value - 1);
}))
.subscribe();
function trackPendingObservable(obs) {
pendingCountSubject.next(pendingCountSubject.value + 1);
pendingSubject.next(obs);
}
trackPendingObservable(httpCall1);
trackPendingObservable(httpCall2);
trackPendingObservable(httpCall3);
And in template:
Pending calls: {{ pendingCount$ | async }}
If I get it right, your service has several methods that perform http calls, e.g.
httpCall1(): Observable<any> { // do stuff}
httpCall2(): Observable<any> { // do stuff}
httpCallN(): Observable<any> { // do stuff}
Somehow these methods run in parallel, maybe because they are called in a rapid synchronous sequence.
What you want to show is how many of them are "on fly" at a certain moment.
Something like this may work.
The service exposes the http call methods as well as a counter of the calls currently "on fly".
export class MyHttpService {
constructor(private http: HttpClient)
public callsOnFly = 0;
httpGenericCall(httpCall: Observable<any>) {
this.callsOnFly++;
return httpCall
.pipe(
finalize(() => this.callsOnFly--)
)
}
httpCall1() {
return this.httpGenericCall(this.http.get('http://my-server1'));
}
httpCall12() {
return this.httpGenericCall(this.http.get('http://my-server2'));
}
httpCallN() {
return this.httpGenericCall(this.http.get('http://my-serverN'));
}
}
The component can just display via interpolation the value of the counter
Pending calls: {{ httpService.callsOnFly }}
where I assume that the name of MyHttpService in the component is httpService
I'm running into an issue with some code for an Ionic 3 app.
Basically, I have a list of objects that all have a unique id. The unique id for each object must be sent through a GET request so that we can get the appropriate data back for each object from the server. This has to be done on a per object basis; I can't bundle them into one request because there is no API endpoint for that.
Therefore the objects are all stored in an array, so I've been trying to loop through the array and call the provider for each one. Note that the provider is returning an observable.
Since the provider is an asynchronous function the promise will resolve before the loop is finished, unless I time out the promise resolution. This defeats the whole point of the promise.
What is the correct way that I should go about doing this so that the looping provider calls are done before the promise resolves?
If I have an inner promise to resolve when the looping is done, won't it also resolve prematurely?
I also read that it is bad to have a bunch of observables open. Should I instead return each observable as a promise using toPromise()?
Here is the code to build the data:
asyncBuildData() {
var promise = new Promise((resolve, reject) => {
let completedRequests = 0;
for (let i = 0; i < 10; i++) {
this.provider.getStuffById(listOfStuff[i].id).subscribe(val => {
list.push(val)
completedRequests++;
})
}
console.log('cp', completedRequests); // completedRequests = 0
setTimeout(() => {
console.log('cp', completedRequests); // completedRequests = 10
let done = true;
if (done) {
resolve('Done');
} else {
reject('Not done');
}
}, 1500)
})
return promise;
}
Code from Provider:
getStuffById(stuffId) {
let url = url + stuffId;
return this.http.get(url)
.map(res => res.json());
}
Even though you can't bundle them into one request, you can still bundle them into one observable, of which those requests are fired in parallel, using .forkJoin():
buildData$() {
let parallelRequests = listOfStuffs.map(stuff => this.provider.getStuffById(stuff.id));
return Observable.forkJoin([...parallelRequests]);
}
and then in your component, you can just call:
buildData$.subscribe(val=>{
//val is an array of all the results of this.provider.getStuffById()
list =val;
})
Note that Obersvable.forkJoin() will for all requests to complete before emitting any values.
If I understand correctly then the following code should get you on your way. This will execute a promise, one at a time, for each element in the array.
var ids = [1,2,3,4,5];
ids.reduce(function (promise, id) {
return promise.then(function () {
let url = url + id;
return this.http.get(url)
.map(res => res.json());
});
}, Promise.resolve()).then(function(last) {
// handle last result
}, function(err) {
// handle errors
});
I tested this with a jQuery post and replaced it with yours from Ionic. If it fails then let me know.