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.
Related
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.
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
I am having some troubles trying to set up a database for testing purposes. The data stored in the database should be removed an re-populated for each test. I am currently doing the following:
db.js
const mongoose = require('mongoose');
// a Mongoose model describing an entity
const Entity = require('entity-model');
// entities.mock is an array containing entity objects.
const mockedEntities= require('./entities.mock');
function setUp() {
Entities.collection.insertMany(mockedEntities);
}
function breakDown() {
mongoose.connection.on('connected', () => {
mongoose.connection.db.dropDatabase();
});
}
module.exports = { setUp, breakDown };
Then in my test.js:
const db = require('./db');
describe('e2e tests to make sure all endpoints return the correct data from the database', () => {
beforeEach(async () => {
await db.breakDown();
db.setUp();
});
it('should check store-test-result (UR-101)', (done) => ...perform test);
it('should check store-nirs-device (UR-102)', (done) => ...perform test);
});
It seems like I am not emptying out the database before re-populating it correctly. Any advise as to what could be the cause?
I ended up doing:
beforeEach(async () => {
await MyEntity.collection.drop();
await MyEntity.collection.insertMany(mockedMyEntity);
});
This solved my issue.
In case this result in an Mongo Error ns not found you need to explicitly create the collection in the database before dropping it. This happens if the collection does not exist. You can do this by adding a before:
before(async () => {
await MyEntity.createCollection();
});
Do not set option: autoCreate to true in you model as this should not be set to false in production according to https://mongoosejs.com/docs/guide.html#autoCreate.
I am trying to write a unit test that should perform an integration test between a REST endpoint and the controller belonging to it. The test should mock the call to the database so no database connection is established during testing.
I am using chai-http to make the HTTP call to the endpoint and sinon with sinon-mongoose to mock the Mongoose models calls.
const set = [{ _id: 1 }, { _id: 2 }, { _id: 3 }];
//Require the dev-dependencies
const sinon = require('sinon');
const { describe, it } = require('mocha');
require('sinon-mongoose');
const chai = require('chai');
const chaiHttp = require('chai-http');
const server = require('../src/server');
const should = chai.should();
// set up mocks
const MyModel = require('../src/models/myModel');
const MyModelMock = sinon.mock(MyModel);
MyModelMock.expects('find').yields(set);
chai.use(chaiHttp);
describe('My endpoints', () => {
describe('/GET to my endpoint', () => {
it('it should GET all the info I want', (done) => {
chai.request(server)
.get('/api/myEndpoint')
.end((err, res) => {
res.should.have.status(200);
done();
});
});
});
});
Googling this error did not yield any results that I am able to work with. What am I doing wrong here?
In case someone ever runs into this (most likely future me).
I managed to solve my issue. I was using promises in my code and should have set up my mock accordingly (also chaining correctly).
MyModelMock.expects('find').chain('where').chain('in').chain('exec').resolves(set);
I'm new to unit testing, can't wrap my head what I should unit test given this function ? Note that I don't want to hit database, so I need to mock/stub mongoose built in function "findById" maybe ? Can't figure it out big picture here :).
Not asking for complete solution, just an idea or some pointer to go from there.
LyricSchema.statics.like = function(id) {
const Lyric = mongoose.model('lyric');
return Lyric.findById(id)
.then(lyric => {
++lyric.likes;
return lyric.save();
})
}
Thank you !
Mockgoose
https://github.com/mockgoose/mockgoose
But this creates a in memory Database. And the database will serve the actual calls.
This is simple tdd test. I'm use Mocha + chai:
const chai = require('chai');
const expect = chai.expect;
const mongoose = require('mongoose');
const Lyric= mongoose.models.Lyric;
describe("Lyric test", () => {
let lyricId;
//create lyric document
beforeEach(done => {
Lyric.create({...})
.then(result=>{
lyricId = result._id;
done();
})
});
//remove lyric document after test
afterEach(done=>{
Lyric.remove({_id: lyricId})
.then(()=>{
done();
})
});
//test like function
it('like test', done=>{
Lyric.like(lyricId)
.then(result=>{
expect(result.likes).equal(1);
done();
})
.catch(err=>{throw err;})
});
})