Writing JEST unit test for a simple function - javascript

Basically this is my react code
getDetails: function () {
var apiUrl = ConfigStore.get('api')
request
.get(apiUrl)
.set('X-Auth-Token', AuthStore.jwt)
.set('Accept', 'application/json')
.end(function (err, response) {
if (!err) {
if(response.text.indexOf("string") > -1){
this.dispatch('COMMAND1', response);
}
else {
this.dispatch('COMMAND2', response.body.options);
}
}
else {
this.dispatch('COMMAND3', response && response.body);
}
}.bind(this));
}
I've written an unit test for the above function of COMMAND1
it('Getting Latest Details', () => {
let eventSpy = sinon.spy();
require('superagent').__setMockResponse({
body: {
firstName: 'blah',
lastName: 'm ',
username: 'blah',
text: {
text : jest.fn()
}
}
});
let dispatchListener = AppDispatcher.register((payload) => {
if (payload.action.type === 'COMMAND1') {
eventSpy(payload.action.payload);
}
});
AuthStore.loggedIn = jest.genMockFunction().mockReturnValue(true);
AuthStore.getToken = jest.genMockFunction().mockReturnValue('545r5e45er4e5r.erereere');
MedsAlertsActions.getDetails();
expect(eventSpy.called).toBe(true);
dispatch('COMMAND1', data);
AppDispatcher.unregister(dispatchListener);
});
When i run
npm test myfile.test
I'm getting
TypeError: Cannot read property 'indexOf' of undefined
So how do i put the indexOf the response in my body? How to resolve the type error
How to write test cases for command2 and command3 as well.

I can see you're using sinon. You can create a sandbox and a fake server in it who return the response expected for each test case. Something like this, for example:
describe('your test suite', () => {
let sandbox;
let server;
beforeAll(() => {
sandbox = sinon.sandbox.create();
server = sandbox.useFakeServer();
});
it('Calls COMMAND1', () => {
//Sinon takes some ms to respond, so you have to use a setTimeout
setTimeout(
() => server.respond([200, { 'Content-Type': 'text/html' }, 'some string']),
0
);
// Put here your assertions
});
});
You can use server.restore() and sandbox.restore() to clean each one when you needed. Besides, you can access the requests made with sandbox.requests
Here's a great post that may help you: https://medium.com/#srph/axios-easily-test-requests-f04caf49e057, it's about axios, but you can implement it in the same way.
Also, you can know more about it at the official sinon documentation for sandboxes: http://sinonjs.org/releases/v1.17.7/sandbox

Related

Jest test is not passing through any parameters (becomes 'undefined')

EDIT: The variable username becomes undefined for some reason. I tried passing other variables too, but none of them appear as they should with console.log().
I have a jest file ("index.test.js") for testing my API:
'use strict';
const request = require('supertest');
const app = require('./index');
describe('Test login', () => {
test('POST /login', () => {
return request(app)
.post('/login')
.send({username: 'preset1'}) //suggestion by #acincognito
.expect(301)
});
});
and a corresponding POST route in my nodejs file ("index.js"):
...
function contains(arr, key, val) {
for (var i = 0; i < arr.length; i++) {
if(arr[i][key] === val) {
return true
};
}
return false;
}
app.post("/login", async (req, res) => {
try {
var username = req.body.username;
const data = await readFile("results.json");
var json = JSON.parse(data);
if (contains(json, "name", username) === true){
...
return res.redirect(301,"/");
} else {
return res.redirect(401,"/");
}
} catch (error) {
return res.redirect("/");
}
});
JSON file ("results.json") has the following format:
[
{"name":"preset1","otherstuff":[]},
...
{"name":"preset5","otherstuff":[]}
]
I am getting the error message :
expected 301 "Moved Permanently", got 401 "Unauthorized"
NOTE: When I run the code manually on a local server, everything works as it should which seems to contradict the output of the test.
Add a console.log(json) to your app.post function to see what gets parsed.
After looking at the docs for supertest: apparently .set('username','preset1') is for setting headers in your request, but according to your app.post username is inside the request's body, therefore try .send({username: 'preset1'}) .
I needed to encode a Javascript Object into a string.
function serialize(obj){
return Object.keys(obj).map(k => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`).join('&');
}
// Code from : https://stackoverflow.com/questions/1714786/query-string-encoding-of-a-javascript-object
Instead of:
.send({username: 'preset1'})
Replace with:
.send(serialize({username: 'preset1'}))
Edit: This did not work for all parameters.

How to encapsulate mocha `expect()` code?

I'm trying to test for the presence of some api response properties that I want to require across all tests (a status and data property).
Here's a generic test that asserts the desired properties in a supertest expect() method:
it('should create a widget', done => {
let status = 200;
request(test_url)
.post('/api/widgets')
.set('Authorization', `Bearer ${token}`)
.send({
sku: my_widget_data.sku,
name: my_widget_data.name,
description: ''
})
.expect(res => {
assert(
Object.keys(res.body).includes('status'),
'`status` is a required field'
);
assert(
Object.keys(res.body).includes('data'),
'`data` is a required field'
);
assert.strictEqual(res.body.status, status);
assert.strictEqual(res.status, status);
})
.end((err, res) => {
if (err) return done(err);
done();
});
});
This expect() behavior is going to be common to almost all my tests.
How can I extract the expect() behavior to DRY up my tests, while still passing arbitrary status numbers?
You could extrapolate the function that expect() calls into another one which returns a function you pass status into:
export function statusCheck(status) {
return res => {
assert(
Object.keys(res.body).includes("status"),
"`status` is a required field",
)
assert(Object.keys(res.body).includes("data"), "`data` is a required field")
assert.strictEqual(res.body.status, status)
assert.strictEqual(res.status, status)
}
}
Now in your original file, you could call:
.expect(statusCheck(200))
Here's a snippet showing how it works as well:
// Ignored since status is scoped below
const status = 400
// Returns your (res) => {} function, uses status
function statusCheck(status) {
return res => {
console.log(`Desired status number is ${status}`)
if(res.status === status) console.log(`Response: ${res.status}, It worked!`)
else console.log(`Response: ${res.status}, It failed!`)
}
}
// Testing if it works with a mockup
const thisGoesInsideExpect = statusCheck(200)
const res = {status: 200}
thisGoesInsideExpect(res)

node.js timeout restarts api call

Platform:
I have an api in sails.js and a frontend in react. The calls between front and back end are being made with fetch api.
More information:
In the course of some api endpoints I have to execute an external file, at this point I am using the execfile() function of node.js, and I have to wait for it to be executed to respond to the frontend.
What is the problem?
If the file is executed in a short time, for example less than 1 minute everything runs well and the behavior occurs as expected on both sides, but if (in this case) the file takes more than 1 minute to execute, there is something to trigger a second call to api (I do not know where this is being called, but I tested the same endpoint with postman and I did not have this problem so I suspect the react / fetch-api) and the api call with the same data from the first call is redone. This causes the file to run twice.
Something that is even stranger is that if you have the DevTools Network inspector turned on this second call does not appear, but nothing in the documentation of sailjs points to this behavior.
Example of an endpoint in sails.js:
/**
* FooController
*/
const execFile = require("child_process").execFile;
module.exports = {
foo: async (req, res) => {
let result = await module.exports._executeScript(req.body).catch(() => {
res.status(500).json({ error: "something has occurred" });
});
res.json(result);
},
_executeScript: body => {
return new Promise((resolve, reject) => {
let args = [process.cwd() + "/scripts/" + "myExternalFile.js", body];
let elem = await module.exports
._execScript(args)
.catch(err => reject(err));
resolve(elem);
});
},
_execScript: args => {
return new Promise((resolve, reject) => {
try {
execFile("node", args, { timeout: 150000 }, (error, stdout, stderr) => {
if (error || (stderr != null && stderr !== "")) {
console.error(error);
} else {
console.log(stdout);
}
let output = { stdout: stdout, stderr: stderr };
resolve(output);
});
} catch (err) {
reject(err);
}
});
}
};
Example of component react with fetch call:
import React from "react";
import { notification } from "antd";
import "./../App.css";
import Oauth from "./../helper/Oauth";
import Config from "./../config.json";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
syncInAction: false,
data: null
};
}
componentDidMount() {
this.handleSync();
}
async handleSync() {
let response = await fetch(Config.apiLink + "/foo/foo", {
method: "POST",
mode: "cors",
headers: {
Authorization: Oauth.isLoggedIn(),
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(this.state.myData)
}).catch(err => {
notification.error({
key: "catch-ApiFail",
message: "Erro"
});
});
let json = await response.json();
this.setState({
syncInAction: false,
data: json
});
}
render() {
return <div>{this.state.data}</div>;
}
}
export default App;
What is my expected goal / behavior:
It does not matter if the call takes 1 minute or 10 hours, the file can only be called once and when it finishes, then yes, it can return to the frontend.
Note that the examples do not have the original code and have not been tested. Is a simplified version of the code to explain the behavior
I ended up solving the problem, apparently nodejs has a default timing of 2 minutes on the server, and can be rewritten to miss this timout or increase it.
This was just adding a line of code at the beginning of the foo() endpoint and the problem was solved.
The behavior of redoing the call is that it is not documented, and it is strange not to have this behavior when using the postman, but here is the solution for whoever needs it.
Final result:
foo: async (req, res) => {
req.setTimeout(0);
let result = await module.exports._executeScript(req.body).catch(() => {
res.status(500).json({ error: "something has occurred" });
});
res.json(result);
};

Node.js - How to test HTTPS requests with promise

I started writing unit tests for my nodeJs application so i could learn about this concept.
After writing some basic tests for simple functions (using Mocha and Chai) i want to move on to some more complex tests.
I have written a simple piece of code that can make a request using node's HTTPS module. That code looks like this:
const https = require('https')
module.exports.doRequest = function (params, postData) {
return new Promise((resolve, reject) => {
const req = https.request(params, (res) => {
let body = []
res.on('data', (chunk) => {
body.push(chunk)
})
res.on('end', () => {
try {
body = JSON.parse(Buffer.concat(body).toString())
} catch (e) {
reject(e)
}
resolve(body)
})
})
req.on('error', (err) => {
reject(err)
})
if (postData) {
req.write(JSON.stringify(postData))
}
req.end()
})
}
Now i want to invoke this method with the following parameters:
const PARAMS = {
host: 'jsonplaceholder.typicode.com',
port: 433,
method: 'GET',
path: `/todos/1`,
headers: {
Authorization: 'Bearer 123'
}
}
And make the request like so:
getTodos = (PARAMS) => {
return doRequest(PARAMS).then((result) => {
if (result.errors) { throw result }
return {
'statusCode': 200,
'body': JSON.stringify({ message: result.title }),
}
}).catch((error) => ({
'statusCode': error.statusCode,
'body': JSON.stringify({ message: error.message }),
}
))
}
Now my question is how i can test this bit of code properly. I have looked on how to tackle this with the Nock.js libary but i don't have a good understanding on where to start.
If anyone can point me in the right direction on how to start with writing some tests for this bit of code i will be thankfull.
In general, you would want to black box your HTTP handling, so that as few modules in your application need to care about the details of HTTP as possible.
In the source folder, you'd have one module (e.g. commonhttp.js). You want this to export your HTTP functions, and other modules in your application use them like this:
const commonhttp = require('./commonhttp');
commonhttp.doRequest( ... ).then( ... );
Other modules, like todos.js, and various other modules, will export their own functions using that module, for example:
const commonhttp = require('./commonhttp');
const todos = {
getTodos( ... ) {
return commonhttp.doRequest( ... );
},
createTodo( ... ) {
return commonhttp.doRequest( ... );
},
// etc.
};
module.exports = todos;
For your unit tests, when you test the todos.js module, you want to mock any calls to the commonhttp module; you can use simple mocha + Sinon for this, and spy on the doRequest method. Basically all you're testing is "when I call getTodos, I expect it to make a call to doRequest with these arguments". You'd follow this pattern for all the modules in your application that uses doRequest.
You also, of course, want to test the commonhttp module -- that spec is where Nock might come in handy. It's not strictly necessary, you can also "block-box" the http module, but you have to set up a lot of complicated spies to mimic the behavior of http; instead, writing a spec (using Nock) that says "ok, I call doRequest with these params, that should have made this HTTP call" does make sense.

CORS POST request throws on Authorization header

I'm trying to send a CORS POST request to my API and it throws a TypeError every time I use the 'Authorization' header. The request doesn't even get sent, so the server is not involved. But this only happens in my tests. When I try it in Chrome it works just fine.
Here is the function that I'm testing:
export const postNewEmployee = formData => {
return fetch('http://localhost:3003', {
method: 'POST',
headers: {
'Authorization': 'Bearer test123',
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => response)
.catch(error => {
throw error;
});
};
And its test:
import * as API from './api';
describe('postNewEmployee', () => {
it('posts the form data asynchronously', () => {
let formData = {
employee: {
name: 'Test Person',
email: 'test#person.nu',
address: 'an adress 123'
}
};
return API.postNewEmployee(formData)
.then(json => {
expect(json.status).toEqual(201);
}).catch(error => {
console.log(error);
});
});
});
The application is a react/redux app created with create-react-app, so I'm using Jest and JSDOM to test this. The thing is, if I comment out the Authorization header from the fetch()-call, it works fine. But if I add that header I get this:
TypeError: Cannot convert undefined or null to object
at Object.getRequestHeader (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xhr-utils.js:20:23)
at setDispatchProgressEvents (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:909:38)
at XMLHttpRequest.send (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:700:11)
at /Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/whatwg-fetch/fetch.js:429:11
at Object.<anonymous>.self.fetch (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/whatwg-fetch/fetch.js:373:12)
at Object.<anonymous>.exports.postNewEmployee.formData [as postNewEmployee] (/Users/johanh/Kod/react-app/src/api/api.js:20:10)
at Object.it (/Users/johanh/Kod/react-app/src/api/api.test.js:75:16)
at Object.<anonymous> (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/build/jasmine-async.js:42:32)
at attemptAsync (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/vendor/jasmine-2.4.1.js:1919:24)
at QueueRunner.run (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/vendor/jasmine-2.4.1.js:1874:9)
And as I said, this only happens in the test. In the browser it works fine.
I feel like I'm missing something obvious here, but I just can't see it. I've looked in the fetch spec and the jsdom documentation, but to no avail. Any ideas?
Normally you should not make real requests in a unit test. The best way to handle this is to use a mock instead of the real fetch implementation.
I assume you are using the JS implementation of fetch. So you can set fetch to what ever you want in your test.
import * as API from './api';
describe('postNewEmployee', () => {
it('posts the form data asynchronously', () => {
// set fetch to a mock that always returns a solved promise
const fetch = jest.fn((url, options) => return Promise.resolve({status: 201}))
global.fetch = fetch;
let formData = {
employee: {
name: 'Test Person',
email: 'test#person.nu',
address: 'an adress 123'
}
};
//test that fetch was called with the correct parameters
expect(fetch.mock.calls[0][0]).toBe('http://localhost:3003')
expect(fetch.mock.calls[0][1]).toEqual(formData)
return API.postNewEmployee(formData)
.then(json => {
expect(json.status).toEqual(201);
}).catch(error => {
console.log(error);
});
});
});

Categories

Resources