how to test the following code in jasmine - angular6 - jasmine

We have a service api layer. The angular app calls the api to get or post the required information
I have a service function which uses map and mergemap. First to post and then get the data.
I am not sure how to write the test case for that.
This is the code:
saveAndReloadData(refNo: string):Observable<DataResponseModel> {
return this.http.post(this._remoteApiUrl + '/Savedata',this.initialdetails)
.map((res) => {
if(res['hasErrors']){
console.log('Error while saving the data', res);
return throwError(res['resultDescription']);
} else {
return res;
}
}).catch((error: any) => this.handleError(error))
.mergeMap((saveResp) => {
return this.http.get(this._remoteApiUrl + '/GetDetails?refNo=' + refNo)
})
.map((getRes) => {
this.FinalDetails = getRes
return getRes;
}).catch((error: any) => this.handleError(error)); ```

This is how you can test it, please ask any doubts when you write for the other testing branches for this code.
desc("saveAndReloadData method", () => {
it('should set FinalDetails', fakeAsync(() => {
component.FinalDetails = null;
httpServiceMock.post.and.returnValue(of({hasErrors: false}));
httpServiceMock.get.and.returnValue(of({test: 1}));
let output = '';
component.saveAndReloadData.subscribe();
flush();
expect(component.FinalDetails).toEqual({test: 1});
}));
});

Related

.pipe(takeUntil) is listening when it is not supposed to

We are using .pipe(takeUntil) in the logincomponent.ts. What I need is, it should get destroyed after successful log in and the user is on the landing page. However, the below snippet is being called even when the user is trying to do other activity and hitting submit on the landing page should load different page but the result of submit button is being overridden and taken back to the landing page.
enter code hereforkJoin({
flag: this.auth
.getEnvironmentSettings('featureEnableQubeScan')
.pipe(take(1)),
prefs: this.auth.preferences.pipe(take(1)),
}).subscribe(
(result: any) => {
this.qubeScanEnabled = result.flag.featureEnableQubeScan;
this.userPrefs = result.prefs;
// check to see if we're authed (but don't keep listening)
this.auth.authed
.pipe(takeUntilComponentDestroyed(this))
.subscribe((payload: IJwtPayload) => {
if (payload) {
this.auth.accountO
.pipe(takeUntilComponentDestroyed(this))
.subscribe((account: IAccount) => {
if (this.returnUrl) {
this.router.navigateByUrl(this.returnUrl);
} else {
this.router.navigate(['dashboard']);
}
}
}
}
}
);
ngOnDestroy() {}
Custom Code:
export function takeUntilComponentDestroyed(component: OnDestroy) {
const componentDestroyed = (comp: OnDestroy) => {
const oldNgOnDestroy = comp.ngOnDestroy;
const destroyed$ = new ReplaySubject<void>(1);
comp.ngOnDestroy = () => {
oldNgOnDestroy.apply(comp);
destroyed$.next(undefined);
destroyed$.complete();
};
return destroyed$;
};
return pipe(
takeUntil(componentDestroyed(component))
);
}
Please let me know what I am doing wrong.
Versions:
rxjs: 6.5.5
Angular:10.0.8
Thanks
I've done a first pass at creating a stream that doesn't nest subscriptions and continues to have the same semantics. The major difference is that I can move takeUntilComponentDestroyed to the end of the stream and lets the unsubscibes filter backup the chain. (It's a bit cleaner and you don't run the same code twice every time through)
It's a matter of taste, but flattening operators are a bit easier to follow for many.
enter code hereforkJoin({
flag: this.auth
.getEnvironmentSettings('featureEnableQubeScan')
.pipe(take(1)),
prefs: this.auth.preferences.pipe(take(1)),
}).pipe(
tap((result: any) => {
this.qubeScanEnabled = result.flag.featureEnableQubeScan;
this.userPrefs = result.prefs;
}),
mergeMap((result: any) => this.auth.authed),
filter((payload: IJwtPayload) => payload != null),
mergeMap((payload: IJwtPayload) => this.auth.accountO),
takeUntilComponentDestroyed(this)
).subscribe((account: IAccount) => {
if (this.returnUrl) {
this.router.navigateByUrl(this.returnUrl);
} else {
this.router.navigate(['dashboard']);
}
});
This function doesn't create another inner stream (destroyed$). This way is a bit more back to the basics so it should be easier to debug if you're not getting the result you want.
export function takeUntilComponentDestroyed<T>(comp: OnDestroy): MonoTypeOperatorFunction<T> {
return input$ => new Observable(observer => {
const sub = input$.subscribe({
next: val => observer.next(val),
complete: () => observer.complete(),
error: err => observer.error(err)
});
const oldNgOnDestroy = comp.ngOnDestroy;
comp.ngOnDestroy = () => {
oldNgOnDestroy.apply(comp);
sub.unsubscribe();
observer.complete();
};
return { unsubscribe: () => sub.unsubscribe() };
});
}

Axios one step behind vue's v-model

I've trouble finding out why my method logs the step before the actual one. So If I select 1 in a box and then 2, I'll get printed out nothing then 1.
Here is my code :
<b-form-select #input="setCadeauOrReduction(vGiftCard)" #change="calculateNet(vGiftCard)" v-if="this.cadeauOrReduction != 'reduction'" v-model="vGiftCard" id="bonCadeauSoin">
<option></option>
<option v-for="boncadeau in boncadeaus" :key="boncadeau.id" v-bind:value="boncadeau.id">
<p>N° </p>
{{boncadeau.serialNumberProduct}}
<p>|</p>
{{boncadeau.amountOfCard}}
<p>CHF</p>
</option>
</b-form-select>
This basically calls the function #change. It gives me the Gift card's id as parameter. Then the function it calls :
fetchOneBonCadeau(idToFetch)
{
axios.get('/bonCadeaus/' + idToFetch)
.then((res) => {
this.bonCadeauPourAxios = res.data
})
.catch((err) => {
console.log(err);
})
return this.bonCadeauPourAxios;
},
//Calculer montant net
calculateNet(value)
{
console.log(this.fetchOneBonCadeau(value));
if(this.vReasonReduction)
{
this.vCostNet = this.vCostBrut - this.vCostBrut * this.vReasonReduction.reductionAmount;
}
else
{
this.vCostNet = this.vCostBrut;
}
}
The console.log part always lags one step behind. I can't figure why. This is my controller if needed :
public function show($id)
{
$bonCadeau = BonCadeau::where('id', $id)->first();
return $bonCadeau;
}
Edit : normal code using the vModel binding property
fetchOneBonCadeau(idToFetch)
{
axios.get('/bonCadeaus/' + idToFetch)
.then((res) => {
this.bonCadeauPourAxios = res.data
})
.catch((err) => {
console.log(err);
})
},
//Calculer montant net
calculateNet(value)
{
this.fetchOneBonCadeau(value);
console.log(this.bonCadeauPourAxios); //Is one step behind, first value is empty
if(this.vReasonReduction)
{
this.vCostNet = this.vCostBrut - this.vCostBrut * this.vReasonReduction.reductionAmount;
}
else
{
this.vCostNet = this.vCostBrut;
}
}
I feel like vGiftCard is updated after the function "calculateNet" is called
The reason is that the result of the HTTP request returned by Axios is asynchronous, you will not obtain it right away in the fetchOneBonCadeau function.
What you can do however is return the axios promise from fetchOneBonCadeau and use it in calculateNet.
So you can implement fetchOneBonCadeau like this:
fetchOneBonCadeau(idToFetch)
{
return axios.get('/bonCadeaus/' + idToFetch)
.then(res => res.data)
.catch(err => {
console.log(err);
})
},
And calculateNet like this:
calculateNet(value)
{
this.fetchOneBonCadeau(value).then( (bonCadeauPourAxios) => {
console.log(bonCadeauPourAxios);
if(this.vReasonReduction)
{
this.vCostNet = this.vCostBrut - this.vCostBrut * this.vReasonReduction.reductionAmount;
}
else
{
this.vCostNet = this.vCostBrut;
}
});
)
}
Implementing the logic using the bonCadeauPourAxios variable in the "then" callback guaranties that the variable will have been retrieved from the backend.

TypeError: You provided an invalid object where a stream was expected

The following code works. It does an ajax request and then call 2 actions, on at a time:
export const loadThingsEpic = action$ => {
return action$.ofType(LOAD_THINGS)
.mergeMap(({things}) => {
const requestURL = `${AppConfig.serverUrl()}/data/things`;
return ajax.getJSON(requestURL)).map(response => {
return finishLoadingThings(response);
}).map(() => {
return sendNotification('success');
});
})
.catch(e => {
return concat(of(finishLoadingThings({ things: {} })),
of(sendNotification('error')));
});
}}
But this code does not:
export const loadThingsEpic = action$ => {
return action$.ofType(LOAD_THINGS)
.mergeMap(({things}) => {
const requestURL = `${AppConfig.serverUrl()}/data/things`;
return ajax.getJSON(requestURL).switchMap(response => {
return concat(of(finishLoadingThings(response)),
of(sendNotification('success')));
});
})
.catch(e => {
return concat(of(finishLoadingThings({ things: {} })),
of(sendNotification('error')));
});
}
I've replace the map by a switchMap to merge 2 actions together (as seen in many other post). It works in the catch if an exception is thrown. I'm wondering whats wrong with the code. I'm guessing it's because I can't seem to really grasp when to use: map, swicthMap and mergeMap.
sendNotification and finishLoadingthings returns action object:
export function finishLoadingThings(data: any) {
return {
type: FINISH_LOADING_THINGS,
data,
};
}
Thanks!
The code provided as-is appears to work as intended: https://jsbin.com/becapin/edit?js,console I do not receive a "invalid object where stream expected" error when the ajax succeeds or fails.
Are you sure the error is coming from this code?
On a separate note, you might be happy to hear that Observable.of supports an arbitrary number of arguments, each one will be emitted after the other. So instead of this:
.switchMap(response => {
return concat(of(finishLoadingThings(response)),
of(sendNotification('success')));
});
You can just do this:
.switchMap(response => {
return of(
finishLoadingThings(response),
sendNotification('success')
);
});
This would not have caused a bug though, it's just cleaner.
I manage to fix my problem, by doing the switchMap at the same level than the mergeMap. Like this:
export const loadThingsEpic = action$ => {
return action$.ofType(LOAD_THINGS)
.mergeMap(({things}) => {
const requestURL = `${AppConfig.serverUrl()}/data/things`;
return ajax.getJSON(requestURL).switchMap(response => {
return of(response);
});
})
.switchMap((res) => {
return concat(of(finishLoadingThings(res.value)),
of(sendNotification('success')));
})
.catch(e => {
return concat(of(finishLoadingThings({ things: {} })),
of(sendNotification('error')));
});
}
Don't quite get it yet.

Chaining AJAX requests - Angular 2 and ES2015 Promises

In Angular 1 I could do something like:
(pseudo code)
/* part 1 */
function myFn1(){
var x = $http.get('myurl');
x.then(
() => {}, // do something here
() => {} // show error here
);
return x;
}
/* part 2 */
myFn1().then(()=>{
$q.all($http.get('url'), $http.get('url2'), $http.get('url3'))
.then(()=>{ /* do something */ });
});
I know how to replicate part 1 in Angular 2
let myFn = () => {
return new Promise((res, rej) => {
this.http.get('myurl')
.subscribe((success) => {
// do something here
res(success);
}, (error) => {
// show error here
rej(error);
});
});
}
However the code in 2nd example looks much uglier and much less readable for me.
Question 1: Can I do it better/nicer way?
Following that logic I can wrap all GET requests (part 2) in promises and than chain it but again this doesn't seem to be nice and clean way of doing that.
Question 2: how I can nicely chain requests in angular 2 without wrapping every single request in promise.
You can leverage observables for this. It's not necessary to use promises...
In series (equivalent to promise chaining):
this.http.get('http://...').map(res => res.json())
.flatMap(data => {
// data is the result of the first request
return this.http.get('http://...').map(res => res.json());
})
.subscribe(data => {
// data is the result of the second request
});
In parallel (equivalent to Promise.all):
Observable.forkJoin([
this.http.get('http://...').map(res => res.json()),
this.http.get('http://...').map(res => res.json())
])
.subscribe(results => {
// results of both requests
var result1 = results[0];
var result2 = results[1];
});
Regarding error handling and part1, you can migrate things like this:
/* part 1 */
function myFn1(){
return this.http.get('myurl').map(res => res.json())
.map(data => {
// do something
return data;
})
.do(data => {
// do something outside the data flow
})
.catch(err => {
// to throw above the error
return Observable.throw(err);
});
}
As for part2, you can still use Promises:
myFn1().then(() => {
return Promise.all(
$http.get('url').toPromise().then(res => res.json()),
$http.get('url2').toPromise().then(res => res.json()),
$http.get('url3').toPromise().then(res => res.json())
).then(() => {
/* do something */
});
});

ECMAScript 6 Chaining Promises

I'm trying to chain promises, but the second one doesn't call the resolve function. What do I do wrong?
function getCustomers(){
let promise = new Promise((resolve, reject) => {
console.log("Getting customers");
// Emulate an async server call here
setTimeout(() => {
var success = true;
if (success) {
resolve( "John Smith"); // got the customer
} else {
reject("Can't get customers");
}
}, 1000);
}
);
return promise;
}
function getOrders(customer) {
let promise = new Promise((resolve, reject) => {
console.log("Getting orders");
// Emulate an async server call here
setTimeout(() => {
var success = true;
if (success) {
resolve("Order 123"); // got the order
} else {
reject("Can't get orders");
}
}, 1000);
}
);
return promise;
}
getCustomers()
.then((cust) => getOrders(cust))
.catch((err) => console.log(err));
console.log("Chained getCustomers and getOrders. Waiting for results");
The code prints "Getting orders" from the second function, but doesn't print "Order 123":
Getting customers
Chained getCustomers and getOrders. Waiting for results
Getting orders
Update. I wanted to insert the print on the console between chained methods that return promises. I guess something like this is not possible:
getCustomers()
.then((cust) => console.log(cust)) //Can't print between chained promises?
.then((cust) => getOrders(cust))
.then((order) => console.log(order))
.catch((err) => console.error(err));
You want to chain a success handler (for your resolve result "Order 123"), not an error handler. So use then instead of catch :-)
getCustomers()
.then(getOrders)
.then((orders) => console.log(orders))
.catch((err) => console.error(err));
None of the promises was rejected, so the console.log(err) in your code was never called.
I wanted to insert the print on the console between chained methods that return promises. I guess something like this is not possible:
getCustomers()
.then((cust) => console.log(cust)) //Can't print between chained promises?
.then((cust) => getOrders(cust))
Yes it is possible, but you are intercepting a chain here. So the second then callback actually is not called with cust, but with the result of the first then callback - and console.log returns undefined, with which getOrders will get some problems.
You'd either do
var customers = getCustomers();
customers.then(console.log);
customers.then(getOrders).then((orders) => …)
or simpler just
getCustomers()
.then((cust) => { console.log(cust); return cust; })
.then(getOrders)
.then((orders) => …)
Here is a code example for Sequential execution for node.js using ES6 ECMAScript. Maybe somebody finds it useful.
http://es6-features.org/#PromiseUsage
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
var soapClient = easysoap.createClient(params);
//Sequential execution for node.js using ES6 ECMAScript
console.log('getAllFunctions:');
soapClient.getAllFunctions()
.then((functionArray) => {
return new Promise((resolve, reject) => {
console.log(functionArray);
console.log('getMethodParamsByName:');
resolve();
});
})
.then(() => {
return soapClient.getMethodParamsByName('test1'); //will return promise
})
.then((methodParams) => {
console.log(methodParams.request); //Console log can be outside Promise like here too
console.log(methodParams.response);
console.log('call');
return soapClient.call({ //Return promise
method: 'test1',
params: {
myArg1: 'aa',
myArg2: 'bb'
}
});
})
.then((callResponse) => {
console.log(callResponse); // response data as json
console.log('end');
})
.catch((err) => {
throw new Error(err);
});

Resources