nodejs/mocha/chai as promise : variables used in expected async function initialized outside - node.js

I am brand new to mocha/chai and I spent 2 days trying to solve the following issue without any success (please note that the code below is just to present the concept, it is not the real one).
I have got a JS file called "api.js" in which some variables such as SERVER_URL are initialized at the top of the file through dotenv framework.
api.js :
const SERVER_URL = process.env.SERVER_URL;
async function startAPI () {
return new Promise ( (resolve, reject) => {
console.log(`${SERVER_URL}`);
resolve();
});
exports = {startAPI};
Now I have got "test.js" file in which :
test.js:
require('../api');
it('the test', async () => {
return await expect(api.startAPI()).to.be.fulfilled;
});
The problem is that SERVER_URL is undefined during the test and I cannot modify the api.js (as I am not the owner), just the test.js.
How can I run the test with the SERVER_URL variable set correctly (to process.env.SERVER_URL value from api.js) ?
Is there a solution without any refactoring ?
And if not what is the best solution ?
Experts, thanks in advance for your precious help

A way to improve testability is to use process.env.SERVER_URL instead of SERVER_URL where possible - or getServerUrl():
const getServerUrl = () => process.env.SERVER_URL;
This way process.env.SERVER_URL can be mocked at any point.
An alternative is to import module after process.env.SERVER_URL was mocked. This should involve decaching if there's more than one test that uses this module, because it won't be re-evaluated otherwise.
const decache = require('decache');
...
let originalServerUrl;
beforeEach(() => {
originalServerUrl = process.env.SERVER_URL;
});
beforeEach(() => {
process.env.SERVER_URL = originalServerUrl;
});
it('the test', async () => {
decache('../api');
process.env.SERVER_URL = '...';
const api = require('../api');
await expect(api.startAPI()).to.be.fulfilled;
});
If it's expected that there's no SERVER_URL in tests, it can be just discarded after it was mocked:

The easiest way would be just to set these variables when you run your test from CLI:
e.g. in npm scripts:
"scripts": {
"test": "SERVER_URL='http://example.com' mocha"
}
or directly from terminal:
$ SERVER_URL='http://example.com' npm test
But better solution would be mock environment variables in your tests with little refactoring. And need proxyquire to be installed. And actually async/await is not needed here.
const proxyquire = require('proxyquire').noPreserveCache() // noPreserveCache is important to always have refreshed script with new process.env.SERVER_URL in each test
const MOCKED_SERVER_URL = 'http://example.com'
describe('example', () => {
let initialServerUrl
let api
beforeEach(() => {
initialServerUrl= process.env
})
afterEach(() => {
process.env = initialServerUrl
})
it('fulfilled', () => {
process.env.USE_OTHER_CODE_PATH = MOCKED_SERVER_URL
api = proxyquire('../api', {})
return expect(api.startAPI()).to.be.fulfilled
})
it('rejected', () => {
process.env.USE_OTHER_CODE_PATH = ''
api = proxyquire('../api', {})
return expect(api.startAPI()).to.be.rejected
})
})

You can set .env variables with mocha using the following line:
env SERVER_URL=htt://api.yourserver.com/ mocha test
This way mocha knows what to expect from your process.env.SERVER_URL

Related

How to cover unit test of "if statement" in jest dependeing of external variable in nodejs

I have the following Javscript code:
const authenticationTypeMapping = (payload) => {
const { API_CONFIG } = process.env;
try {
const apiConfig = JSON.parse(API_CONFIG.toString('utf8'));
// set authenticationType to Federated for production
if (apiConfig.API_BASE_URL.includes('prd')) {
payload.authenticationTypeName = 'Federated';
// set authenticationType to Federated for dev or UAT
} else if (apiConfig.API_BASE_URL.includes('dev') || apiConfig.API_BASE_URL.includes('uat')) {
payload.authenticationTypeName = 'Basic';
}
} catch (err) {
console.log(`Failed to map authenticationType. Unable to parse Secret: ${err}`);
}
return payload;
};
I have problem to cover unit test using jesty for the code for the lines inside try block.
If statement depends on external variable "apiConfig.API_BASE_URL" of "process.env" which I don't how to represent to jest code.
it('should call authenticationTypeMapping', async () => {
const payload = mapper.authenticationTypeMapping(basicPayload);
expect(payload.authenticationTypeName).toEqual('Basic');
});
What should be added to cover the unit test?
You can set the Environment in the test and check for the same condition in the unit test as follows
it('should call authenticationTypeMapping', async () => {
process.env.API_BASE_URL = 'prd...'
expect(mapper.authenticationTypeMapping(basicPayload).authenticationTypeName).toEqual('Federated');
process.env.API_BASE_URL = 'dev...'
expect(mapper.authenticationTypeMapping(basicPayload).authenticationTypeName).toEqual('Basic');
});
Maybe you can have more than one unit tests to make things clear like one to test 'prd' and one to test 'dev'

How can a determine if a plugin has been registered or not, given a HapiJS server object?

I have code that loads a plugin dynamically based on environment.
Because I am using jest --coverage, and 100% coverage is required, I need to test if the plugin was loaded or not given the environment conditions set in the NODE_ENV.
All I am looking for here is how to determine if a particular plugin has been registered or not, after I compose the server object with Glue() and a manifest file that has logic to include the plugin only when NODE_ENV is not "production".
In case someone else is trying to figure this out, here is what I ended up doing:
describe('GIVEN NODE_ENV is production', () => {
describe('WHEN service is initialized', () => {
let server;
const OLD_ENV = process.env;
beforeEach(async () => {
jest.resetModules();
process.env = { ...OLD_ENV };
process.env.NODE_ENV = 'production';
server = await initialize();
});
afterEach(async () => {
process.env = OLD_ENV;
await server.stop();
server = null;
});
test('THEN swagger plugin should not be added', () => {
expect(server.registrations['hapi-swagger']).not.toBeDefined();
});
});
});

How to test a module in NestJs

I'm adding tests on a project and improving coverage. I would like to know how can I test a module definition (mymodule.module.ts file) in NestJs.
In particular, I'm testing a main module that imports other modules, one of them inits a db connection, this means that I need to mock the service on the other module, to avoid a real db connection.
At this moment I have something like this:
beforeEach(async () => {
const instance: express.Application = express();
module = await NestFactory.create(MyModule, instance);
});
describe('module bootstrap', () => {
it('should initialize entities successfully', async () => {
controller = module.get(MyController);
...
expect(controller instanceof MyController).toBeTruthy();
});
});
This works, but I'm sure this can get improvements :)
The ideal thing would be something like overrideComponent method provided by Test.createTestingModule.
P.S: I'm using 4.5.2 version
Improved my tests based on #laurent-thiebault answer:
import { Test } from '#nestjs/testing';
import { ThingsModule } from './things.module';
import { ThingsResolver } from './things.resolver';
import { ThingsService } from './things.service';
describe('ThingsModule', () => {
it('should compile the module', async () => {
const module = await Test.createTestingModule({
imports: [ThingsModule],
}).compile();
expect(module).toBeDefined();
expect(module.get(ThingsResolver)).toBeInstanceOf(ThingsResolver);
expect(module.get(ThingsService)).toBeInstanceOf(ThingsService);
});
});
You can now test your module (at least for code coverage) by building it that way:
describe('MyController', () => {
let myController: MyController;
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [MyModule],
}).compile();
myController = module.get<MyController>(MyController);
});
it('should the correct value', () => {
expect(myController.<...>).toEqual(<...>);
});
});
We usually didn't test .module.ts files directly.
we do this in e2e testing.
but I wonder why one should test the the module ! and you are trying to test if the module can initialize it's components , it should.
but i recommend you do this in e2e testing.
in my opinion, in unit testing you should focus on testing the services or other components behaviors not the modules.

How to mock module function using proxyquire

I need to mock 'mkdirp-promise' node module which exposes a constructor function as below
mkdirpPromise(dirPath)
.then(() => {
console.log('ABCDEFGH');
resolve(dirPath);
})
.catch((error) => {
console.log('HeABCDEFGHre');
const details = error.message;
const err = customError.failed_create_downloads_directory()
.withDetails(details);
reject(err);
});
Im able to mock it using proxiquire as below for the first time:-
let mkdirpPromiseMock = sinon.stub().rejects();
const sthreeDownloadMock =
proxyquire('./../../modules/sThreeDownload', {
joi: joiMock,
fs: fsMock,
'#monotype/core-error': {
errors: {
ApiError: customErrorMock,
},
},
'aws-sdk': awsSDK,
'mkdirp-promise': mkdirpPromiseMock,
path: pathMock,
});
Now i want to override mkdirpPromiseMock in 2nd test case with
mkdirpPromiseMock = sinon.stub().resolves();
which im not able to. Any help is appreciated.
Proxyquire is not compatible with jest.
You need to use a mocking library like rewiremock.
Please have a look at this answer which goes into detail.
REPL example

Mock.mockImplementation() not working

I have a service class
Service.js
class Service {
}
export default new Service();
And I am trying to provide a mock implementation for this. If I use something like this:
jest.mock('./Service', () => { ... my mock stuff });
It works fine, however I'm not able to access any variables declared outside of the mock, which is a bit limiting as I'd like to reconfigure what the mock returns, etc.
I tried this (inspired by this other StackOverflow article: Service mocked with Jest causes "The module factory of jest.mock() is not allowed to reference any out-of-scope variables" error)
import service from './Service';
jest.mock('./Service', () => jest.fn);
service.mockImplementation(() => {
return { ... mock stuff }
);
Unfortunately when I am trying to run this, I get the below error:
TypeError: _Service2.default.mockImplementation is not a function
I had same problem as #Janos, the other answers didn't help either. You could do two things :
If you need to mock only a function from Service, in your test file:
import service from './Service';
jest.mock('./Service', () => jest.fn());
service.yourFunction = jest.fn(() => { /*your mock*/ })
 
If you need to mock the entire Module:
Say your service.js is in javascript/utils, create a javascript/utils/_mocks_ and inside it create a service.js file, you can then mock the entire class in this file, eg:
const myObj = {foo: "bar"}
const myFunction1 = jest.fn(() => { return Promise.resolve(myObj) })
const myFunction2 = ...
module.exports = {
myFunction1,
myFunction2
}
then in your test file you just add:
jest.mock('./javascript/utils/service')
...functions exported from the mockfile will be then hit through your test file execution.
The mock is equal to jest.fn. You need to call jest.fn to create a mocked function.
So this:
jest.mock('./Service', () => jest.fn);
Should be:
jest.mock('./Service', () => jest.fn());
ran into similar issues and resolved it by using .mockImplementationOnce
jest.mock('./Service', () => jest.fn()
.mockImplementationOnce(() => {
return { ... mock stuff }
})
.mockImplementationOnce(() => {
return { ... mock other stuff }
})
);
now when you run another test it will return the second mock object.
You need to store your mocked component in a variable with a name prefixed by "mock" and make sure you return an object with a default property as you import your Service from the default in your "main.js" file.
// Service.js
class Service {
}
export default new Service();
// main.test.js (main.js contains "import Service from './Service';")
const mockService = () => jest.fn();
jest.mock('./Service', () => {
return {
default: mockService
}
});
I had similar problem, and the cause was that ".spec.js" file had an
import jest from "jest-mock";
After removing this line, it worked.
My mistake was that I was resetting the mock before each test. If you do that, be sure to reconfigure the mock implementation.
For example, change this:
let value;
let onPropertyChange: OnPropertyChangeCallback = jest.fn((changes: any) => {
value = changes["testValue"];
});
const user = userEvent.setup();
beforeEach(() => {
jest.resetAllMocks();
});
to this:
let value;
let onPropertyChange: OnPropertyChangeCallback;
const user = userEvent.setup();
beforeEach(() => {
jest.resetAllMocks();
onPropertyChange = jest.fn((changes: any) => {
value = changes["testValue"];
});
});

Resources