I am using the jsonapi-serializer library to deserialize API data. I promisified the callback using angular's $q constructor and wrapped that in a service, this works fine on the browser, but when I test it using jasmine on the karma runner, the promise doesn't resolve. This is the method on the service (Notice I'm using TypeScript)
public deserialize(type: string, data: any): any {
// get the predefined options for resource type
let deserializeOpts: any = this.deserializeOpts[type];
// use jsonapi-serializer
// the options are not working
let deserializer: any = new JAS.Deserializer({});
console.log(data);
// return a promise with the parsed object
return this._$q((resolve: any, reject: any) => {
deserializer.deserialize(data, (err: any, result: any) => {
if (result) {
console.log(resolve);
resolve(result);
} else {
console.log(err);
reject(err);
}
});
});
}
This is my test after a while trying to debug it
it('should flatten jsonapi user', function (done) {
var deserialized;
JsonapiParser.deserialize(type, apiUser).then(
(result) => {
deserialized = result;
expect(deserialized).toEqual(apiUser);
done();
}
);
});
An this is a sample use of the mentioned deserializer service
// returns the promise so controller can display the errors
return this.$http.get(url)
.then(
(response: any) => {
if (response.data.data.length !== 0) {// deserialize data
return this._deserializer.deserialize('activities', response.data) // the deserializer service is called;
} else { // throw an error if data is empty
return this.$q.reject({ error: this.ACTIVITY.empty });
}
},
() => {
return this.$q.reject({ error: this.ACTIVITY.connectionError });
}
).then(
(deserialized: any) => { // data is copied to original list so it doesn't lose it's bindings
angular.copy(deserialized, this.list); // the result from the deserializer is used
console.log(deserialized);
return this.list;
});
This last block of code works fine when compiled and run on the browser. But the tests get timed out. If I log inside the deserialize method, I can see that the callback gets resolved, but the promise never seems to digest. If I place a $rootScope.$digest() after the call to resolve, the test works, but I don't want to hardcode that in there, especially since the code is working when deployed.
You are close with $rootScope.$digest(), however instead of triggering the digest from the app code, trigger it from the test with $rootScope.$apply().
See Testing Promises and Service Testing.
this works fine on the browser
I can't believe that. You are never calling resolve! Instead of
console.log(resolve);
you need to use
console.log(result);
resolve(result);
Btw, the typical node-style callback promisification is using if (err), not if (result). Maybe you want if (err || !result).
Related
I'm trying to use the AngularFire library in an Angular application. Some of the AngularFire calls return promises, and I'd like to handle them as observables instead for consistency throughout the app. I'm using rxjs v6
Using from() works well and gives the expected behaviour except when errors occur.
If the promise throws an exception, the observable doesn't seem to see it and a stack trace gets dumped in the console saying Error: Uncaught (in promise).
My first attempt
The AngularFire call that returns the promise:
deleteCampaign(id: string) {
return from(this.campaignCollection.doc(id).delete());
}
The calling code:
deleteCampaign(id: string) {
return this.dataStorageService.deleteCampaign(id)
.pipe(
catchError(
err => {
console.log('error when deleting campaign');
console.log(err);
return throwError(err);
}
)
);
}
In this instance, I get the stack trace in the console and the catchError never fires.
My second attempt
I added a catch to the promise inside the from, and then tried rethrowing the error as an observable so it looked like this:
deleteCampaign(id: string) {
return from(this.campaignCollection.doc(id).delete().catch(
err => {
throwError(err);
}
));
}
My third attempt
Much like the second attempt, but I tried throwing a plain javascript error. This resulted in the same stack trace however, and it wasn't picked up by the observable either.
deleteCampaign(id: string) {
return from(this.campaignCollection.doc(id).delete().catch(
err => {
throw(err);
}
));
}
This stopped the stack trace happening, as now the promise was catching it, but the calling code still never sees the error.
Am I going about this the wrong way? I assumed that by using from() all of the error handling could occur in the observable, and I could leave the promise alone.
I need to be able to either:
1. Have no error handling code where the promise is returned and let the observable take care of it.
1. Have the promise catch block able to rethrow an error thats caught by the observable.
Here's the solution arrived at:
From the front end component, handle the passed error using the error callback in subscribe
onDelete(id: string) {
this.loadingCampaigns = true;
this.campaignService.deleteCampaign(id).subscribe(
_ => {},
err => {
console.log('error detection from the component');
}
);
}
From the campaign service, tap() the error so it can be logged or otherwise:
deleteCampaign(id: string) {
return this.dataStorageService.deleteCampaign(id)
.pipe(
tap(null, () => {console.log('tapped the error');} ),
);
}
Finally, from the data storage component do nothing at all:
deleteCampaign(id: string) {
return from(this.campaignCollection.doc(id).delete());
}
You can attach an error callback to Observable.subscribe().
Rx.Observable.from(Promise.reject('Boo!'))
.subscribe(val => {
console.log('success');
},
err => {
console.log(err);
});
// Boo!
deleteCampaign(id: string) {
return from(this.campaignCollection.doc(id).delete()).pipe(catchError(err=>{
return throwError(err);
}))
}
deleteCampaign(myid).susbcribe(res=>{
console.log(res);
},error=>{
console.log(error)
})
I put an example using ng-bootstrap modal -that return a promise when open the modal- to convert to a Observable in this stackblitz
I have a TypeScript project which I would like to deploy as JS NPM package. This package performs some http requests using rxjs ajax functions. Now I would like to write tests for these methods.
At some point I have a method like this (simplified!):
getAllUsers(): Observable<AjaxResponse> {
return ajax.get(this.apiUrl + '/users');
}
I know about basic testing, for example with spyOn I can mock a response from the server. But how would I actually test the http request?
The documentation of jasmine says that I cannot do async work in the it part, but in the beforeEach: https://jasmine.github.io/tutorials/async
Would this be the correct approach to test the API?
let value: AjaxResponse;
let error: AjaxError;
beforeEach((done) => {
const user = new UsersApi();
user.getAllUsers().subscribe(
(_value: any) => {
value = _value;
done();
},
(_error: any) => {
error = _error;
done();
}
);
});
it("should test the actual http request", () => {
// Test here something
// expect(value).toBe...
// expect(error).toBe...
});
I couldn't think of another approach how to do the async work...
You need to mock ajax.get to return an Observable that emits values that you want to test.
This is done depending on how ajax is declared in your file that contains user.getAllUsers method.
It'd be ideal if UsersApi() had ajax passed into it (pure function style) because then you could just do something like this:
e.g.
class UsersApi {
public ajax;
constructor(ajax) {
this.ajax = ajax;
}
getAllUsers() {
return this.ajax.get(....)
}
}
Edit: Passing in dependencies (aka dependency injection) is one thing that makes modules like this significantly easier to test - consider doing it!
Then you could very easily mock your tests out like this:
const someSuccessfulResponse = ...
const someFailedResponse = ...
const ajaxWithSuccess = {
get:jest.fn(() => of(someSuccessfulResponse))
}
const ajaxWithFailed = {
get:jest.fn(() => throwError(someFailedResponse))
}
describe('my test suite',() => {
it("should test a successful response", (done) => {
const user = new UsersApi(ajaxWithSuccess);
user.getAllUsers().subscribe(d => {
expect(d).toBe(someSuccessfulResponse);
done();
});
});
it("should test a failed response", (done) => {
const user = new UsersApi(ajaxWithFailed);
user.getAllUsers().subscribe(null,error => {
expect(d).toBe(someFailedResponse);
done();
});
});
});
Note: You DO NOT want to test the actual API request. You want to test that your code successfully handles whatever API responses you think it could receive. Think about it, how are you going to test if a failed API response is handled correctly by your code if your API always returns 200s?
EDIT #27: The above code works fine for me when I run jest, not totally clear on why jasmine (doesn't jest run on jasmine?) says it can't do async in it's. In any case, you could just change the code above to set everything up in the beforeEach and just do your expects in the it's.
I have my JS set up as follows.
App1.js
function x(){
thirdPartyLibrary.performAsyncTask((resultObject, errorObject) => {
// This is a callback function, invoked when performAsyncTask is done.
// I can handle resultObject and errorObject here.
});
}
Let's say I have other files as part of this app. Each of them calls x(), and would invoke their own version of a handleSuccess() or handleError() function depending on the results of the call to x().
How can I structure the call to x() such that I can achieve this? It's almost like I want to "listen" to the results of performAsyncTask() from App1.js, but I'm not sure how to do that.
If you need to stay with callbacks (due to very old JS clients or whatever), you can provide the callback as parameter to x:
function x(myCallback) {
thirdPartyLibrary.performAsyncTask(myCallback);
}
// other file:
x((resultObject, errorObject) => {
// handle just like before
});
You could even change it to two callbacks, depending on the result. Only one of your callbacks will be called in the end:
function x(successCallback, errorCallback) {
thirdPartyLibrary.performAsyncTask((resultObject, errorObject) => {
if (errorObject) return errorCallback(errorObject);
else return successCallback(resultObject);
});
}
// other file:
x(
function handleSuccess(resultObject) {
// handle success
},
function handleError(errorObject) {
// handle error
}
);
Have x return a Promise that resolves with resultObject if there's no error, or rejects with errorObject if there is an error. Then, callers of x can chain .then onto the Promise to handle successes, and chain .catches to handle failures:
function x(){
return new Promise((resolve, reject) => {
thirdPartyLibrary.performAsyncTask((resultObject, errorObject) => {
if (errorObject) reject(errorObject);
else resolve(resultObject);
});
});
}
x()
.then(result => {
// handle successful result
})
.catch(err => {
// handle error
});
So what I want to achieve is that when a function returns an empty Object from the Promise, the Await function must not be executed and the rest of the Application must carry on executing other tasks. As the object that is returned maybe not always be available but should be returned when available.
function getData(Data) : Promise<Object> {
return new Promise((resolve, reject) => {
request({
// Method
}, (err, resp, file)=> {
if (err) {
reject(err);
} else {
resolve({
// Return object infomation
});
}
});
});
}
let someData = await Promise.all(data.map(getData));
// This should have a part that ignores if getData is empty and this await function ignored.
The rest of the application should be able to run as normal. I have tried to use:
.catch(error => { });
But didn't work the way I wanted it to work
There may be a better way, but what solved my issue was to pass an empty array to the data
if (isNullOrUndefined(data)) {
data = [];
}
In this way the await function now works the way I want it to work and does not throw error:
TypeError: Cannot read property 'map' of undefined
I just want to ask how should I pass the resolve promise to catch if the value on the resolve is not intended.
e.g.
let prom = getPromise();
prom.then(value => {
if (value.notIWant) {
// Send to catch <-- my question is here, I want to pass it on the catch.
}
// Process data.
}).catch(err => {
// Pass the error through ipc using json, for logging.
});
I tried to using throw but the object cant be parsed to json and just got an empty object.
ANSWER:
#BohdanKhodakivskyi first comment below is the answer I want.
#31py answer is also correct but the #BohdanKhodakivskyi solution is much simplier and will render the same result.
Simply use throw value;. In your case:
prom.then(value => {
if (value.notIWant) {
// Send to catch
throw value;
}
// Process data.
}).catch(err => {
// Pass the error through ipc using json, for logging.
});
Please also note the difference and limitations between using Promise.reject() and throw which is perfectly described in this question. For example, throw will not work in some async scenarios.
You can simply return a rejected promise:
prom.then(value => {
if (value.notIWant) {
return Promise.reject('your custom error or object');
}
// Process data.
}).catch(err => {
console.log(err); // prints 'your custom error or object'
});
.catch actually handles any promise rejection in the chain, so if you're returning a rejected promise, the control automatically flows to catch.
why you just not rethrow the error? throw new Error("something");
You can use outside functions to do it:
var processData = function(data) {
// process data here
}
var logIt = function(data) {
// do logging here..
}
let prom = getPromise();
prom.then(value => {
if (value.notIWant) {
// Send to catch <-- my question is here, I want to pass it on the catch.
logIt(/*pass any thing*/);
}
// Process data.
processData(data);
}).catch(err => {
logIt(/*pass any thing*/);
});