I'm trying to test my controller (express middleware) with Jest. In order to explain my problem, I'll provide my code:
import request from 'utils/request';
import logger from 'config/logger';
const get = async (req, res, next) => {
try {
const response = await request.get('entries?content_type=category');
return res.json(response.data);
} catch (error) {
logger.error(error.response.data.message);
return next(error);
}
};
module.exports = {
get,
};
I need to test this get function. In order to do so, I need to provide the req, res and next args to it. I've found this question where the op talks about mocking the express request and then says "how to use it" but I can't see how. That's the only topic that I've seen so far directly related with my problem but the answer doesn't work for me (I don't want to use Nock nor any other library to adchieve the testing, just Jest).
So, how can I successfully test this function? Is it by using mocking or is there any other way?
Sorry for my bad english and thanks in advance for your help!
If you are writing unit tests, then mocking is the more appropriate way to go. using Jest, Mocking should be available out of the box. In a test file, it may look something like this:
import request from 'utils/request';
import logger from 'config/logger';
import { get } from 'middleware/get'; // Or whatever file you are testing
jest.mock('request'); // Mocking for a module import
jest.mock('logger'); // Mocking for a module import
const mockReq = () => {
const req = {};
// ...from here assign what properties you need on a req to test with
return req;
};
const mockRes = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
test('should return a json response of data', () => {
const mockedNext = jest.fn();
const mockedReq = mockReq();
const mockedRes = mockRes();
const mockedEntries = {
data: {}
};/*...whatever mocked response you want back from your request*/
request.get.mockResolvedValue(mockedEntries);
const result = get(mockedReq, mockedRes, mockedNext);
expect(result).to.equal(mockedEntires.data);
expect(mockedNext.mock.calls.length).toBe(1);
expect(mockedRest.json).toHaveBeenCalledWith(mockedRes.data)
});
Jest Mocking
Related
I'm trying to write out a unit test that stubs the getSignedUrl function from the #aws-sdk/s3-request-presigner package, however when I try stub out the function with sinon, I receive the error:
TypeError: Descriptor for property getSignedUrl is non-configurable and non-writable
const s3RequestSigner = require("#aws-sdk/s3-request-presigner");
const expect = require('chai').expect;
const sinon = require('sinon')
....
it('should throw an error when getSignedUrl rejects', async function() {
const sandbox = sinon.createSandbox();
sandbox.stub(s3RequestSigner, "getSignedUrl").rejects("fakeUrl");
sandbox.restore();
})
I'm using node.js 16 and writing javascript rather than typescript. Is there a way to mock out my function, i'm struggling to write my tests otherwise?
I came up with the following workaround for ES6 modules. You can wrap getSignedUrl in your own module and mock that module instead. This approach should work for any modules where sinon is unable to mock a "non-configurable and non-writable" method.
For example:
my-s3-client-internals.js - Your custom wrapper module
// You'll need to import the original method, assign it to
// a new const, then export that const
import { getSignedUrl as getSignedUrl_orig } from '#aws-sdk/s3-request-presigner';
export const getSignedUrl = getSignedUrl_orig;
my-s3-client.js - Consumer of getSignedUrl
// Import the method instead from your custom file
import { getSignedUrl } from './my-s3-client-internals';
// Call it however you normally would, for example:
export const getUrl(bucket, key) {
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
return getSignedUrl(client, command, { expiresIn: 300 });
}
my-s3-client.spec.js - Unit tests for the consumer module
import { getUrl } from './my-s3-client';
import * as clientInternals from './my-s3-client-internals';
import sinon from 'sinon';
it('does something', () => {
// Mock the method exported from your wrapper module
sinon.stub(clientInternals, 'getSignedUrl')
.callsFake(async (client, command, options) => {
return 'fake-url';
});
// Then call your consumer method to test
const url = await getUrl('test-bucket', 'test-key');
expect(url).to.equal('fake-url');
});
So I won't make this the official answer, unless there are no better solutions, but this is what my research has brought about a solution.
The issue is related to this: https://github.com/sinonjs/sinon/issues/2377
Where sinon will throw an error when the Object.descriptor is non-configurable.
There is no obvious way around that currently, that I can find. The way to solve it is to use proxyquire:
const sinon = require('sinon')
const proxyquire = require('proxyquire')
...
it('should throw an error when getSignedUrl rejects', async function() {
const fakeurl = 'hello world'
const fakeURL = sinon.stub().resolves(fakeurl)
const handler = proxyquire(
'../../handlers/presigned_url',
{
'#aws-sdk/s3-request-presigner': {
'getSignedUrl': async () => {
return fakeURL()
}
}
}
)
This will then resolve with whatever you want fakeurl to be.
Another possible solution is to use mockery. E.g. to mock uuid
import { expect } from 'chai';
import mockery from 'mockery';
import sinon from 'sinon';
describe('domain/books', () => {
let createBook;
let uuidStub;
before(async () => {
mockery.enable({
warnOnReplace: false,
warnOnUnregistered: false,
});
uuidStub = sinon.stub();
mockery.registerMock('uuid', { v4: uuidStub });
({ createBook } = await import('../domain/books.js'));
});
afterEach(() => {
sinon.resetHistory();
});
after(() => {
sinon.restore();
mockery.disable();
mockery.deregisterAll();
});
describe('createBook', () => {
it('should save a book and return the id', () => {
const id = 'abc123';
uuidStub.returns(id);
const { id: bookId } = createBook({
title: 'My Book',
author: 'Jane Doe',
});
expect(bookId).to.equal(id);
});
});
});
The mockery setup is a bit tedious, but the library saved me a number of times.
I'm trying to mock a function using Frisby and Jest.
Here are some details about my code:
dependencies
axios: "^0.26.0",
dotenv: "^16.0.0",
express: "^4.17.2"
devDependencies
frisby: "^2.1.3",
jest: "^27.5.1"
When I mock using Jest, the correct response from API is returned, but I don't want it. I want to return a fake result like this: { a: 'b' }.
How to solve it?
I have the following code:
// (API Fetch file) backend/api/fetchBtcCurrency.js
const axios = require('axios');
const URL = 'https://api.coindesk.com/v1/bpi/currentprice/BTC.json';
const getCurrency = async () => {
const response = await axios.get(URL);
return response.data;
};
module.exports = {
getCurrency,
};
// (Model using fetch file) backend/model/cryptoModel.js
const fetchBtcCurrency = require('../api/fetchBtcCurrency');
const getBtcCurrency = async () => {
const responseFromApi = await fetchBtcCurrency.getCurrency();
return responseFromApi;
};
module.exports = {
getBtcCurrency,
};
// (My test file) /backend/__tests__/cryptoBtc.test.js
require("dotenv").config();
const frisby = require("frisby");
const URL = "http://localhost:4000/";
describe("Testing GET /api/crypto/btc", () => {
beforeEach(() => {
jest.mock('../api/fetchBtcCurrency');
});
it('Verify if returns correct response with status code 200', async () => {
const fetchBtcCurrency = require('../api/fetchBtcCurrency').getCurrency;
fetchBtcCurrency.mockImplementation(() => (JSON.stringify({ a: 'b'})));
const defaultExport = await fetchBtcCurrency();
expect(defaultExport).toBe(JSON.stringify({ a: 'b'})); // This assert works
await frisby
.get(`${URL}api/crypto/btc`)
.expect('status', 200)
.expect('json', { a: 'b'}); // Integration test with Frisby does not work correctly.
});
});
Response[
{
I hid the lines to save screen space.
}
->>>>>>> does not contain provided JSON [ {"a":"b"} ]
];
This is a classic lost reference problem.
Since you're using Frisby, by looking at your test, it seems you're starting the server in parallel, correct? You first start your server with, say npm start, then you run your test with npm test.
The problem with that is: by the time your test starts, your server is already running. Since you started your server with the real fetchBtcCurrency.getCurrency, jest can't do anything from this point on. Your server will continue to point towards the real module, not the mocked one.
Check this illustration: https://gist.githubusercontent.com/heyset/a554f9fe4f34101430e1ec0d53f52fa3/raw/9556a9dbd767def0ac9dc2b54662b455cc4bd01d/illustration.svg
The reason the assertion on the import inside the test works is because that import is made after the mock replaces the real file.
You didn't share your app or server file, but if you are creating the server and listening on the same module, and those are "hanging on global" (i.e: being called from the body of the script, and not part of a function), you'll have to split them. You'll need a file that creates the server (appending any route/middleware/etc to it), and you'll need a separate file just to import that first one and start listening.
For example:
app.js
const express = require('express');
const { getCurrency } = require('./fetchBtcCurrency');
const app = express()
app.get('/api/crypto/btc', async (req, res) => {
const currency = await getCurrency();
res.status(200).json(currency);
});
module.exports = { app }
server.js
const { app } = require('./app');
app.listen(4000, () => {
console.log('server is up on port 4000');
});
Then, on your start script, you run the server file. But, on your test, you import the app file. You don't start the server in parallel. You'll start and stop it as part of the test setup/teardown.
This will give jest the chance of replacing the real module with the mocked one before the server starts listening (at which point it loses control over it)
With that, your test could be:
cryptoBtc.test.js
require("dotenv").config();
const frisby = require("frisby");
const URL = "http://localhost:4000/";
const fetchBtcCurrency = require('./fetchBtcCurrency');
const { app } = require('./app');
jest.mock('./fetchBtcCurrency')
describe("Testing GET /api/crypto/btc", () => {
let server;
beforeAll((done) => {
server = app.listen(4000, () => {
done();
});
});
afterAll(() => {
server.close();
});
it('Verify if returns correct response with status code 200', async () => {
fetchBtcCurrency.getCurrency.mockImplementation(() => ({ a: 'b' }));
await frisby
.get(`${URL}api/crypto/btc`)
.expect('status', 200)
.expect('json', { a: 'b'});
});
});
Note that the order of imports don't matter. You can do the "mock" below the real import. Jest is smart enough to know that mocks should come first.
Probably this is a very stupid question, i'm new in Node.js and javascript, so please forgive me if the question is not properly explained or the answer is simple...
I'm trying to send 2 variables thru a url... When i send only 1 variable (artist=${term}) work all good, but I'm really stuck with about how to send 2 variables thru the url (&artist=${term1}&album=${term2})
I've work on this code so far which for 1 variable is working well... but i have no idea how to add a second or a third variable to the request:
File 1: "./services/albumInfo.js"
import { BRV_API } from '../../config';
import axios from 'axios';
import dotenv from 'dotenv';
const ALBUM_INFO = 'method=album.getinfo';
dotenv.config();
const doRequest = async (url) => {
return await axios.get(`${BRV_API}/${url}&api_key=${process.env.API_KEY}&format=json`);
};
export const infoAlbum = async (term) => {
return await doRequest(`?${ALBUM_INFO}&artist=${term}`);
};
File 2: "./repositories/albumInfo.js"
import { infoAlbum } from '../repositories/albumInfo';
import status from 'http-status';
export const albumInfo = async (req, res, next) => {
try {
const { query } = req;
const { data } = await infoAlbum(query.name);
const response = data;
res.status(status.OK).send(response);
} catch (error) {
next(error);
}
};
I know that my problem is in this part of the code (I guess)
export const infoAlbum = async (term) => {
return await doRequest(`?${ALBUM_INFO}&artist=${term1}&album=${term2}`);
};
I've been searching, and i've seen some solution, like this one, but i just don't understand those solutions or how to apply on my code (sorry for that, im a very new on this)
Any good soul who can help this newbie? (if can explain the why of the solution as well, for understand, will be amazing!!)
Thanks in advance!!
Axios provides parameters that can be added custom as the following
const your_url = process.env.URL
const infoAlbum = await axios.get(your_url,{
params: {
artist: term,
album: term2,
api_key: process.env.API_KEY,
format:'json'
}
})
console.log(infoAlbum.data.args)
note: your_url without any more parameters.
So,
I've found a solution, which is pretty ugly, but so far is working, if someone have a better option, will be amazing to know:
File 1: repositories/albumInfo.js, I've just add the console (as per #Alex028502 suggestion), to know what the code was returning:
import { BRV_API } from '../../config';
import axios from 'axios';
import dotenv from 'dotenv';
const ALBUM_INFO = 'method=album.getinfo';
dotenv.config();
const doRequest = async (url) => {
const fullurl = `${BRV_API}/?${ALBUM_INFO}${url}&api_key=${process.env.API_KEY}&format=json`;
console.log('full url is', fullurl);
return await axios.get(fullurl);
};
export const infoAlbum = async (term) => {
return await doRequest(`&${term}`);
};
File 1: services/albumInfo.js: I change the behaviour of 'infoAlbum' to make the request from his side:
import { infoAlbum } from '../repositories/albumInfo';
import status from 'http-status';
export const albumInfo = async (req, res, next) => {
try {
const { query } = req;
console.log(query);
const { data } = await infoAlbum('artist=' + query.artist + '&album=' + query.album);
const response = data;
res.status(status.OK).send(response);
} catch (error) {
next(error);
}
};
I know that probably this is not the very best way to walk away from the problem, but so far is what i have.... any other better option about how to capture the second or third parameter of the url request and then add them to the final url?
Best!
In a feathersJS service, I have a before hook being ran that expects a certain HTTP header to exist:
src/services/service_name/service_name.hooks.js
const validationHook = () => (context, next) => {
if (!context.params.headers.hasOwnProperty('header-wanted'))
throw new errors.BadRequest();
next(null, context);
};
module.exports = {
before: {
all: [cronValidationHook()],
...
..
.
When testing this service in a generated test file from feathers-cli, however, I haven't found a way to inject headers prior to the before hook being called. The test in question is:
test/services/service_name.test.js
describe('get', () => {
it('should run "id" endpoint', async () => {
const service = app.service('v1/cron');
const resp = await service.get('id', params);
// Assertions exist after this call
});
});
Is there a way to do this that does not require utilizing an HTTP call via node-fetch or requests?
params will be whatever you pass. Just set params.headers to what you would like to test, e.g.
const getParams = {
...params,
headers: { 'header-wanted': 'something' }
};
const resp = await service.get('id', getParams);
I'm using mocha and sinon for nodejs unit tests. I have the following
users.js
const Database = require('./lib/Database');
exports.setupNewUser = (name) => {
var user = {
name: name
};
try {
Database.save(user);
}
catch(err) {
console.error('something failed');
}
}
Database.js
exports.save = (user) => {
console.log(`saving: ${user}`);
};
userTest.js
const sinon = require('sinon');
require('chai').should();
const users = require('../src/users');
describe('users', () => {
it('should log an error when the Database save fails', () => {
var databaseSpy = sinon.spy(Database, 'save').throws(); // this is supposed to work??
users.setupNewUser('Charles');
databaseSpy.should.be.called;
});
});
According to the sinon tutorials I've read, I should be able to create that databaseSpy but I keep getting this error: ReferenceError: Database is not defined
What am I missing?
This seems like it might be a pathing issue. Your require might not be getting the correct path.
users.js
const Database = require('./lib/Database');
Where is /lib/Database in relation with the users.js file? I think that might be a good place to start looking.