I'm trying to make it so when calling test routes in my TypeScript API any code that requires environment variables is just abstracted away so we aren't connecting to any real servers etc.
My initial express code is
router.post("/hello", async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
const value = await getAValue(req.body)
...
}
The getAValue()
function is what uses an environment variable, so I'm writing a test for this route using Supertest like so
import request from "supertest";
import App, { getAValue } from "./src";
describe("POST /hello", () => {
it("Returns 200 on expected input", (done) => {
const payload = {
"foo":"bar"
}
request(App)
.post("/api/hello")
.send(payload)
.expect(200)
.end((err) => {
if (err) {
done(err)
} else {
done();
}
});
});
});
I'm stubbing any required functions using stubs file test/stubs.ts
import sinon from "sinon"
import { getAValue } from "../src"
sinon.stub(getAValue);
and using my test
script in my package.json
to find these stubs as
"test": "mocha -r ts-node/register -r test/stubs.ts --config=test/.mocharc.json 'test/**/*.ts' --exit"
So within the getAValue()
function there is a
import * as env from "env-var";
const value = env.get("VAR_NAME").required().asString();
But I'm somehow unable to mock anything related to this and always get the same error
ERROR: EnvVarError: env-var: "VAR_NAME" is a required variable, but it was not set
I've tried mocking that function, or specifically the call to process.env
but neither work.
What is the right way to mock env vars for Express route testing?
In your situation, I have 2 alternatives: a) stub getAValue() and b) stub env.get(). You can choose it depend on your condition.
I use your code and modify it a little for this complete example:
// 1. File: src/index.ts
import express from 'express';
import * as util from './util';
const app = express();
app.use(express.json());
app.post('/hello', async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
// Need to know whether this function get called.
console.log('/hello called');
// Note: Do not use destructure object to call getAValue().
const value = util.getAValue(req.body);
// Check for return value.
console.log('getAValue return:', value);
// Simplify the request end.
res.end();
});
export default app;
// 2. File: src/util.ts
import env from 'env-var';
const getAValue = (body: any) => {
// Need to know whether this function get called.
console.log('Real getAValue called');
// This is based on your code.
const value = env.get('VAR_NAME').required().asString();
return value;
};
export { getAValue };
// 3. File: test/index.spec.ts
import request from 'supertest';
import app from '../src';
describe('POST /hello', () => {
it('Returns 200 on expected input', (done) => {
const payload = {
'foo':'bar'
}
request(app)
.post('/hello')
.send(payload)
.expect(200)
.end((err) => {
if (err) {
done(err)
} else {
done();
}
});
});
});
Now the important file: test/stubs.ts
Alternative a) stub getAValue()
// 4a File: test/stubs.ts
import sinon from 'sinon';
import * as util from '../src/util';
sinon.stub(util, 'getAValue').callsFake((input: any) => {
console.log('fake getAValue called');
// Input here is: req.body or payload from test.
return input;
});
The result when I run it from terminal:
$ npm run test
> 69006601@1.0.0 test
> mocha -r ts-node/register -r test/stubs.ts 'test/**/*.ts' --exit
POST /hello
/hello called
fake getAValue called
getAValue return: { foo: 'bar' }
✔ Returns 200 on expected input
1 passing (22ms)
In this alternative, the real getAValue function never get called and fake getAValue function get called. You can set test env inside fake function.
Alternative b) stub env.get()
// 4b File: test/stubs.ts
import sinon from 'sinon';
import env from 'env-var';
sinon.stub(env, 'get').callsFake(function (input: any) {
// Input is VAR_NAME.
console.log('fake env.get called: ', input);
// Supply with test env.
return env.from({ VAR_NAME: 'test' }).get(input);
});
The result when I run it from terminal:
$ npm run test
> 69006601@1.0.0 test
> mocha -r ts-node/register -r test/stubs.ts 'test/**/*.ts' --exit
POST /hello
/hello called
Real getAValue called
fake env.get called: VAR_NAME
getAValue return: test
✔ Returns 200 on expected input
1 passing (22ms)
In this alternative, the real getAValue get called, but the real env.get() not get called, so you can set env for test.