I am trying to do unit testing for a simple function which sends a get request, receives a response and then returns a promise object with the success or the failure message. Following is the function:
module.exports.hello = async (event, context) => {
return new Promise((resolve, reject) => {
fetch("https://httpstat.us/429", { headers: { 'Content-Type': 'application/json' } }).then(response => {
console.log(response);
if (response.status == 200) {
return response;
} else {
throw Error(response.status + ": " + response.statusText);
}
}).then(tokenData => {
resolve({ status: 200, body: JSON.stringify({ statusText: 'Success' }) });
}).catch(error => {
reject(error.message);
});
});
};
While unit testing, I am using fetch-mock to mock the call to the api and have a custom response. Following is the code:
it('hello returns failure message', (done) => {
fetchMock.get('*', {
status: 429,
statusText: "Too Many Nothings",
headers: { 'Content-type': 'application/json' }
});
edx.hello(null, null).catch(error => {
expect(error).to.equal('429: Too Many Requests');
}).then(() => {
done();
}).catch(error => {
done(error);
});
});
But this code is not mocking the fetch request as when I print the response text it is "Too Many Requests" which is being sent as a response by the API and not "Too Many Nothings" which is being mocked. I am new to NodeJS. Please tell me what am I doing wrong.
If you use node-fetch package, it's not available at the global scope in Node.js. In order to make fetch-mock work you either have to assign fetch to global object (e.g. by import "node-fetch"; instead of import fetch from "node-fetch";) or make fetch injectable to your tested method.
From http://www.wheresrhys.co.uk/fetch-mock/#usageglobal-non-global:
Global or non-global
fetch can be used by your code globally or locally. It’s important to
determine which one applies to your codebase as it will impact how you
use fetch-mock
Global fetch
In the following scenarios fetch will be a global
When using native fetch (or a polyfill) in the browser
When node-fetch has been assigned to global in your Node.js process (a pattern sometimes used in isomorphic codebases)
By default fetch-mock assumes fetch is a global so no more setup is
required once you’ve required fetch-mock. Non-global fetch library
In the following scenarios fetch will not be a global
Using node-fetch in Node.js without assigning to global
Using fetch-ponyfill in the browser
Using libraries which use fetch-ponyfill internally
Some build setups result in a non-global fetch, though it may not always be obvious that this is the case
The sandbox() method returns a function that can be used as a drop-in
replacement for fetch. Pass this into your mocking library of choice.
The function returned by sandbox() has all the methods of fetch-mock
exposed on it, e.g.
const fetchMock = require('fetch-mock');
const myMock = fetchMock.sandbox().mock('/home', 200); // pass myMock in to your application code, instead of fetch, run it, then...
expect(myMock.called('/home')).to.be.true;
How is fetch imported in your file used by your function?
I've got a really basic (almost) VanillaJs file that was using import fetch from "cross-fetch"; but that meant fetchMock from my test file was being ignored.
Switching to import "cross-fetch/polyfill"; allowed me to have tests that provided mocked fetch data and I could have tests that that accessed real data as well.
Related
There seems to be so many different ways to do this, but I am trying to use just sinon, sinon-test, chai/mocha, axios, httpmock modules. I am not able to successfully mock a GET call made using axios. I want to be able to mock the response from that axios call so the unit test won't actually have to make the external API request.
I've tried setting up a basic unit test by creating a sandbox, and using sinon stub to set up a GET call and specify the expected response. I'm unfamiliar with JavaScript and NodeJS.
// Main class (filename: info.js)
function GetInfo(req, res) {
axios.get(<url>).then(z => res.send(z.data));
}
// Test class (filename: info.test.js)
it ("should return info", () => {
const expectedResponse = "hello!";
const res = sinon.spy();
const aStub = sinon.stub(axios, "get").resolves(Promise.resolve(expectedResponse));
const req = httpMock.createRequest({method:"get", url:"/GetInfo"});
info.GetInfo(req, res);
// At this point, I need to evaluate the response received (which should be expectedResponse)
assert(res.data, expectedResponse); // data is undefined, res.status is also undefined
// How do I read the response received?
});
I need to know how to read the response that is supposed to be sent back (if it is being captured in the first place by sinon).
I'm assuming the response you're wanting to check is the z.data being passed to res.send(z.data)
I don't think your Sinon Spy is being set up correctly.
In your example, res is a function created by sinon. This function won't have a property data.
You probably want to create a spy like this:
const res = {
send: sinon.spy()
}
This gives you a res object which has a spy with the key send. You can then make assertions about the parameters used to call res.send
it ("should return info", () => {
const expectedResponse = "hello!";
const res = {
send: sinon.spy()
};
const aStub = sinon.stub(axios, "get").resolves(Promise.resolve(expectedResponse));
const req = httpMock.createRequest({method:"get", url:"/GetInfo"});
info.GetInfo(req, res);
// At this point, I need to evaluate the response received (which should be expectedResponse)
assert(res.send.calledWith(expectedResponse)); // data is undefined, res.status is also undefined
});
Dont know if this helps but you may not be getting the correct response because resolves is a return with a promise wrap over it.
So by using resolves and inside it a Promise.resolve you are actually returning Promise wrap in a Promise.
Maybe you can try changing the code to the one below.
const aStub = sinon.stub(axios, "get").resolves(Promise.resolve(expectedResponse));
to
const aStub = sinon.stub(axios, "get").resolves(expectedResponse);
I am using NestJS to essentially proxy a request to another api using the HttpService (an observable wrapped Axios library). For example:
return this.httpService.post(...)
.pipe(
map(response => response.data),
);
This works properly when the call is successful; however, if there's an error (4xx), how do I properly return the status and error message?
I've figured out how to do it with promises, but if possible I would like to stay within an observable.
You can use catchError to rethrow the exception with the corresponding status code:
import { catchError } from 'rxjs/operators';
this.httpService.get(url)
.pipe(
catchError(e => {
throw new HttpException(e.response.data, e.response.status);
}),
);
Note that error.response might be null, see the axios docs for all error cases to check for.
Also, axios does not throw for 4xx errors as they might be expected and valid responses from the API. You can customize this behavior by setting validateStatus, see docs.
Hello you can try also like this to get value with rxjs from request
const { data } = await lastValueFrom(
this.httpService.get(`${process.env.URL}/users`, {
headers: {
Authorization: authorization,
},
}),
);
How can I use Async/Await on HttpService using NestJs?
The below code doesn`t works:
async create(data) {
return await this.httpService.post(url, data);
}
The HttpModule uses Observable not Promise which doesn't work with async/await. All HttpService methods return Observable<AxiosResponse<T>>.
So you can either transform it to a Promise and then use await when calling it or just return the Observable and let the caller handle it.
create(data): Promise<AxiosResponse> {
return this.httpService.post(url, data).toPromise();
^^^^^^^^^^^^^
}
Note that return await is almost (with the exception of try catch) always redundant.
Update 2022
toPromise is deprecated. Instead, you can use firstValueFrom:
import { firstValueFrom } from 'rxjs';
// ...
return firstValueFrom(this.httpService.post(url, data))
As toPromise() is being deprecated, you can replace it with firstValueFrom or lastValueFrom
For example:
const resp = await firstValueFrom(this.http.post(`http://localhost:3000/myApi`)
https://rxjs.dev/deprecations/to-promise
rxjs library is most powerful concurrency package that chosen form handling system event like click, external request like get data or delete record and ....
The main concept behind this library is:
handle data that receive in future
therefor you most use 3 argument in observable object like
observablSource.subscribe(
data => { ... },
failure => { ... },
compelete => { ... }
)
but for most backend developer use Promises that comes from ECMAScript 6 feature and is native part of JavaScript.
By default in Angular 4+ and Nest.js use rxjs that support Observable. In technical details you can find a solution for change automatic observable to promise.
const data: Observable<any>;
data.from([
{
id: 1,
name: 'mahdi'
},
{
id: 2,
name: 'reza'
},
])
now you have simulate a request with observable type from server. if you want to convert it to Pormise use chained method like example:
data.toPromise();
from this step you have promised object and form using it better to attach async/await
async userList( URL: string | URLPattern ) {
const userList = await this.http.get<any>( URL ).toPromise();
...
}
try these below one instead of only async-await.
There are some basic reasons behind the deprecation of toPromise.
We know that Promise and Observables that both collections may produce values over time.
where Observables return none or more and Promise return only one value when resolve successfully.
there is a main reason behind the seen
Official: https://rxjs.dev/deprecations/to-promise
Stack:Rxjs toPromise() deprecated
Now we have 2 new functions.
lastValueFrom : last value that has arrived when the Observable completes for more https://rxjs.dev/api/index/function/firstValueFrom
firstValueFrom: you might want to take the first value as it arrives without waiting an Observable to complete for more https://rxjs.dev/api/index/function/lastValueFrom
Following is complete example for working code:
.toPromise() is actually missing
async getAuthToken() {
const payload = {
"SCOPE": this.configService.get<string>('SCOPE'),
"EMAIL_ID": this.configService.get<string>('EMAIL_ID'),
"PASSWORD": this.configService.get<string>('PASSWORD'),
};
const url = this.configService.get<string>('AUTHTOKEN_URL')
const response = await this.httpService.post(
url,
payload
).toPromise();
console.log(response.data);
return response.data;
}
You can just add the .toPromise() at the end of each method call but then you lose the power of observables, like it’s ability to add retry to failed http call by just adding the retry operator.
You can implement this abilities by yourself and create your own module or just use package that already implemented it like this : https://www.npmjs.com/package/nestjs-http-promise
This is my first time writing unit tests - please be nice.
I am trying to write unit tests for two functions. one function is to GET a number from the api, and the other is to POST data.
I am not sure how to do this. I know, I want to use the intern "expect" call and fetch-mock but the rest I want to do in react / javascript. I made a mock response with some data.
My questions are:
How can I use fetch-mock to compare my expected output with what my function is outputting
How does my mock response data relate to fetch-mock?
Again, I have not done this before and i am having a hard time understanding the resources available online (have been researching this for 8+ hours) so i am looking for another opinion
I haven't used fetch-mock before, and there are many ways to go about something like this, but the general process for mocking is to setup the mock, do what you need to do, and tear it down in an after or afterEach. How you actually go about checking whether your GET request worked depends on how you go about making the request in the first place.
A test for a mock request might look something like this. The assumption here is that you have a request method that makes a request and returns a Promise (what you'd get if you did return fetch('some_url')).
import * as fetchMock from 'fetch-mock';
const { describe, it, afterEach } = intern.getPlugin('interface.bdd');
const { expect } = intern.getPlugin('chai');
describe('my suite', () => {
afterEach(() => {
fetchMock.restore();
});
it('should do something', () => {
fetchMock.get('*', { hello: 'world' });
return thingThatDoesAGetRequest.then(response => {
expect(response).to.equal({ hello: 'world' });
});
})
});
Another option would be to wait for the mock request to complete rather than looking at a function return value:
it('should do something', test => {
// Create a promise that will resolve when a request is made
const promise = new Promise(resolve => {
fetchMock.get('*', () => {
resolve();
return { hello: 'world' }
});
});
// Do whatever should be making a call
thingThatDoesAGetRequest();
// Wait for the request promise to resolve, then see if whatever
// you expect to happen has happened
return promise.then(() => {
expect(something).to.equal({ hello: 'world' });
});
})
So I am using this.router to change pages in my client application (the application is communicating with a server I build handling authentication). Unfortunately I am met with a CORS error in my application when I do router.push inside the .then clause. However when I use router.push outside the .then clause I receive no errors. Any idea what is causing this?
signup: function () {
this.$http.post('http://localhost:3000/signup?username='+this.username+'&password='+this.password+'&password2='+this.password2)
.then(response => {
if(response.body== "success"){
this.pass_or_fail=response.body;
// this.$router.push({name:'landing-page'}); this Gives me a CORS error
}
})
// this.$router.push({name:'landing-page'}); this works fine
}
This might not actually be a CORS issue. this inside the .then callback is bound to the callback, and not to the Vue instance.
To test this idea, on the first line of the signup function, do this...
signup: function () {
const vm = this
...
and then inside the callback use vm instead of this...
.then(response => {
...
vm.$router.push(...)
If that works, then make sure to also do vm.pass_or_fail=response.body