Stubbing and restoring auth function with Sinon still results in Mocha test using stub - javascript

We are trying to stub out an authentication middleware in an express app in some but not all of our tests, and we are having trouble making stubbing work for us.
Our mocha test looks something like this:
describe('primaryDeal routes unit test', () => {
describe('Authentication allows for data fetching', () => {
let app;
let getPrimaryDealData;
let queryParams;
let isAuthenticated;
let count = 0;
beforeEach(() => {
// console.log(isAuthenticated);
if (count === 0) {
isAuthenticated = sinon.stub(authCheck, 'isAuthenticated');
isAuthenticated.callsArg(2);
}
app = require('../../lib/index.js');
});
afterEach(() => {
if (count === 0) {
isAuthenticated.restore();
}
app.close();
count++;
});
it(('should send an API request, validate input and return 200 response'), () => {
return chai.request(app)
.get('/api/contracts/')
.then((res) => {
expect(res).to.have.status(200);
});
});
it(('should respond with forbidden'), () => {
app = require('../../lib/index.js');
return chai.request(app)
.get('/api/contracts/')
.catch((res, err) => {
expect(res).to.have.status(403);
});
});
});
});
Our stub works as intended for the first it, but the stub appears to not be restored for the second it and our authentication middleware is not being run. Both tests work if the other is commented out.
We've tried separating these blocks in different files, and in different describe blocks, we've also tried switching the order of the it blocks, and we've tried giving both chai.request(app) separate servers but we are at a loss.
Why could it be that our second it statement isn't calling our Auth middleware?

I recommend you to use sandbox. It's more elegant to use. No need to restore stubs individually. Here is an sample:
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
isAuthenticated = sandbox.stub(authCheck, 'isAuthenticated');
});
afterEach(() => {
sandbox.restore();
});
Also, could you try to add replace
app = require('../../lib/index.js');
to
delete require.cache[require.resolve('../../lib/index.js')]
app = require('../../lib/index.js');
Another thought, maybe you need to use reset not restore in this particular case?
P.S.
It's also good to see index.js source as well

I had the same issue and tried this solution without success. However the delete require.cache[require.resolve('../../lib/index.js')] gave me an idea. I was able to use decache instead of delete require. This resolved the problem for me.
const decache = require('decache');
decache('../../lib/index.js');

Related

How can I reset the jsdom instance when using vitest in order to test a History-based router?

I'd like to do some integration testing of my svelte + page.js-based router using vitest, but I'm running into an issue where the jsdom instance only updates correctly once per test file.
In the following setup, either test will pass when run with .only or if I split each test into its own file. But when they run in sequence, the second one will always fail. Inspecting the DOM with screen.debug() reveals that it's empty, and calls to act or tick don't seem to do anything.
I suspect it has something to do with how jsdom is interacting with the History API, but I'm not sure where to go from here.
Root.svelte
<script>
import page from 'page'
import SignIn from './SignIn/SignIn.svelte'
import Upload from './Upload/Upload.svelte'
import { authenticationToken } from './Root.stores.js'
let currentPage
page('/', () => {
page.redirect('/sign-in')
})
page('/sign-in', () => {
currentPage = SignIn
})
page('/upload', () => {
if ($authenticationToken === null) {
return page.redirect('/sign-in')
}
currentPage = Upload
})
page.start()
</script>
<svelte:component this={ currentPage } />
Root.svelte.test.js
import page from 'page'
import Root from './Root.svelte'
import { authenticationToken } from './Root.stores.js'
it('redirects to sign in when not authenticated', async () => {
vi.spyOn(page, 'redirect')
authenticationToken.set(null)
const { act } = setupComponent(Root)
await act(() => page('/upload'))
expect(page.redirect).toHaveBeenCalledWith('/sign-in')
})
it('displays the upload screen when authenticated', async () => {
authenticationToken.set('token')
const { act } = setupComponent(Root)
await act(() => page('/upload'))
expect(document.getElementById('upload')).toBeInTheDocument()
})
Other Research
The issue is similar to this one in the jest project. In that issue, the recommendation was to call jsdom.reconfigure() in a beforeEach block, but I don't know how to get a hold of the jsdom instance in vitest in order to try that.
Any ideas or alternative approaches welcome, thanks!

How to clean-up / reset redis-mock in an Express Jest test?

I have an app which tallies the number of visits to the url. The tallying is done in Redis. I'm using redis-mock which simulates commands like INCR in memory.
The following test visits the page 3 times and expects the response object to report current as 3:
let app = require('./app');
const supertest = require("supertest");
jest.mock('redis', () => jest.requireActual('redis-mock'));
/* Preceeded by the exact same test */
it('should report incremented value on multiple requests', (done) => {
const COUNT = 3;
const testRequest = function (cb) { supertest(app).get('/test').expect(200, cb) };
async.series([
testRequest,
testRequest,
testRequest
], (err, results) => {
if (err) console.error(err);
const lastResponse = _.last(results).body;
expect(
lastResponse.current
).toBe(COUNT);
done();
});
});
The issue is that if I keep reusing app, the internal "redis" mock will continue getting incremented between tests.
I can side-step this a bit by doing this:
beforeEach(() => {
app = require('./app');
jest.resetAllMocks();
jest.resetModules();
});
Overwriting app seems to do the trick but isn't there a way to clean-up the "internal" mocked module somehow between tests?
My guess is that somehow the '/test' endpoint gets invoked in some other tests in the suite, you could try to run specific parts of your suite using .only or even trying to run the entire suite serially.
To answer the original questions the entire suite must be isolated and consistent either if you are running a specific test case scenario or if you are trying to run the entire suite, thus you need to clear up any leftovers that they could actually affect the results.
So you can actually use the .beforeEach or the .beforeAll methods, provided by Jest in order to "mock" Redis and the .afterAll method for clearance.
A dummy implementation would look like this:
import redis from "redis";
import redis_mock from "redis-mock";
import request from "supertest";
jest.mock("redis", () => jest.requireActual("redis-mock"));
// Client to be used for manually resetting the mocked redis database
const redisClient = redis.createClient();
// Sometimes order matters, since we want to setup the mock
// and boot the app afterwards
import app from "./app";
const COUNT = 3;
const testRequest = () => supertest(app).get("/test");
describe("testing", () => {
afterAll((done) => {
// Reset the mock after the tests are done
jest.clearAllMocks();
// You can also flush the mocked database here if neeeded and close the client
redisClient.flushall(done);
// Alternatively, you can also delete the key as
redisClient.del("test", done);
redisClient.quit(done);
});
it("dummy test to run", () => {
expect(true).toBe(true);
});
it("the actual test", async () => {
let last;
// Run the requests in serial
for (let i = 0; i < COUNT - 1; i++) {
last = await testRequest();
}
// assert the last one
expect(last.status).toBe(200);
expect(last.body.current).toBe(COUNT);
});
});

Mocking node_modules which return a function with Jest?

I am writing a typeScript program which hits an external API. In the process of writing tests for this program, I have been unable to correctly mock-out the dependency on the external API in a way that allows me to inspect the values passed to the API itself.
A simplified version of my code that hits the API is as follows:
const api = require("api-name")();
export class DataManager {
setup_api = async () => {
const email = "email#website.ext";
const password = "password";
try {
return api.login(email, password);
} catch (err) {
throw new Error("Failure to log in: " + err);
}
};
My test logic is as follows:
jest.mock("api-name", () => () => {
return {
login: jest.fn().mockImplementation(() => {
return "200 - OK. Log in successful.";
}),
};
});
import { DataManager } from "../../core/dataManager";
const api = require("api-name")();
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("200 - OK. Log in successful.");
expect(api.login).toHaveBeenCalledTimes(1);
});
});
What I find perplexing is that the test assertion which fails is only expect(api.login).toHaveBeenCalledTimes(1). Which means the API is being mocked, but I don't have access to the original mock. I think this is because the opening line of my test logic is replacing login with a NEW jest.fn() when called. Whether or not that's true, I don't know how to prevent it or to get access to the mock function-which I want to do because I am more concerned with the function being called with the correct values than it returning something specific.
I think my difficulty in mocking this library has to do with the way it's imported: const api = require("api-name")(); where I have to include an opening and closing parenthesis after the require statement. But I don't entirely know what that means, or what the implications of it are re:testing.
I came across an answer in this issue thread for ts-jest. Apparently, ts-jest does NOT "hoist" variables which follow the naming pattern mock*, as regular jest does. As a result, when you try to instantiate a named mock variable before using the factory parameter for jest.mock(), you get an error that you cannot access the mock variable before initialization.
Per the previously mentioned thread, the jest.doMock() method works in the same way as jest.mock(), save for the fact that it is not "hoisted" to the top of the file. Thus, you can create variables prior to mocking out the library.
Thus, a working solution is as follows:
const mockLogin = jest.fn().mockImplementation(() => {
return "Mock Login Method Called";
});
jest.doMock("api-name", () => () => {
return {
login: mockLogin,
};
});
import { DataManager } from "../../core/dataManager";
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("Mock Login Method Called");
expect(mockLogin).toHaveBeenCalledWith("email#website.ext", "password");
});
});
Again, this is really only relevant when using ts-jest, as using babel to transform your jest typescript tests WILL support the correct hoisting behavior. This is subject to change in the future, with updates to ts-jest, but the jest.doMock() workaround seems good enough for the time being.

Jest Mock a function's return value

I have a simple function like this, I would like to mock the return value of authentication.getAccessToken() with a valid accessToken, I am having hard time doing this. Tried different ways but couldn't succeed, Can someone help me on this?
import decodeJWT from "jwt-decode";
import authentication from "#kdpw/msal-b2c-react";
export const testfunction = () => {
const jwt = decodeJWT(authentication.getAccessToken());
var current_time = Date.now() / 1000;
var remaining_time = jwt.exp - current_time;
return "testing done"
}
Following is the unit test which I have been trying, As authentication.getAccessToken() doesn't get any value it is throwing InvalidToken error.
import * as commonUtil from "../commonUtil";
describe('test test function', () => {
it("Mock", () => {
//The following runs but test fails due to null return
const authentication = require("#kdpw/msal-b2c-react");
authentication.getAccessToken = jest.fn().mockReturnValue(false);
expect(commonUtil.testfunction()).toBe(false)
});
});
Error message
Comparing two different types of values. Expected boolean but received null.
You need to import authentication within your test.
See CodeSandbox example. Open with editor to check out the unit tests.
In short, you need to do something like this.
test('test test function', () => {
const resp = { data: '' };
import authentication from "#kdpw/msal-b2c-react";
authentication.getAccessToken = jest.fn().mockReturnValue("eyJ0eXAiOiJKV1QiLCJhbdjhkbE5QNC1jNTdkTzZR...");
expect(commonUtil.testfunction()).toEqual("testing done")
});
Use describe to wrap test cases that uses mocked authentication so the mocked function will only stay local in that specific describe and everything outside it will use the real authentication.getAccessToken().

Using node-mocks-http to test a service no event emitted

I have a simple service I'd like to test, by calling its controller. I thought I'd use node-mocks-http to create a mock request, and look at the results of the response. However, despite all the documentation and sample code, I could not get the response to emit any event ("end", "send" or even "error"), and can't therefore know when to test the output.
Here's a simple function (using Express 4.*):
export function getServiceHealth(req, res) {
let message = 'service has been up for ' + process.uptime() + ' seconds!';
res.status(200).send(message);
}
Here's my test (Jasmine):
import {EventEmitter} from 'events';
import httpMock from 'node-mocks-http';
import {getServiceHealth} from '../../lib/controllers/health/';
describe('Service health integration tests', () => {
it('should get health', done => {
let req = httpMock.createRequest({url: '/health'});
let res = httpMock.createResponse({EventEmitter: EventEmitter});
getServiceHealth(req, res);
res.on('end', () => {
console.log(res._getData());
done();
});
res.on('send', () => {
console.log(res._getData());
done();
});
//setTimeout(() => {console.log(res._getData()); done();}, 1000);
});
});
The only way I ever get this test to finish (without throwing a timeout error) is by uncommenting the setTimeout line - obviously not the right way to go. And when I do, the data in res is exactly what I expect it to be - meaning that other than event firing, everything works ok.
What do I have to do to get the event triggered on res?
PS: on the off chance that this is some ES6 import shenanigan, I tried this:
let res = httpMock.createResponse({EventEmitter: require('events').EventEmitter});
Same result.
PPS: Opened issue on repo
After opening an issue on GitHub, I got the answer:
The property is named eventEmitter - not EventEmitter. So I should have written:
var res = httpMocks.createResponse({eventEmitter: EventEmitter});
A bit confusing, but I hope they'll fix the documentation to reflect it. The issue is still open (as of 12/29/2015) pending documentation.
Make sure the listeners are added before calling getServiceHealth(req, res);
import {EventEmitter} from 'events';
import httpMock from 'node-mocks-http';
import {getServiceHealth} from '../../lib/controllers/health/';
describe('Service health integration tests', () => {
it('should get health', done => {
let req = httpMock.createRequest({url: '/health'});
let res = httpMock.createResponse({EventEmitter: EventEmitter});
res.on('end', () => {
console.log(res._getData());
done();
});
res.on('send', () => {
console.log(res._getData());
done();
});
getServiceHealth(req, res);
//setTimeout(() => {console.log(res._getData()); done();}, 1000);
});
});

Categories

Resources