How to define env variables before import in jest? - node.js

In my unit test, there is an imported module import { DeviceFromDb1 } from '../Device' which is a file related to mongoose schema define.
The reason that makes me run into this question is: in this file, I need to populate data from another database, and this is how I did:
../Device file:
const conn = mongoose.createConnection(
process.env.MONGODB_URI + '/' + process.env.ADMINDB
);
const adminDbModel = getAdminModel(conn); // this function will return the model I want from the adminDb
// Inside Schema definition, I used 'ref: adminDbModel' to do the populate
For unit test, I use MongoMemoryServer. Usually what I do in other unit tests is, open a new MongoMemoryServer and set the uri of it as the Env variable MONGO_URI. However, this approach doesn't work in this unit test as when the module is imported, both MONGODB_URI and ADMINDB are undefined so mongoose.createConnection throw an error "MongoParseError: Invalid connection string "undefined/undefined"". It turns out that this module is loaded before my unit test starts(earlier than beforeEach, beforeAll).
Approach I tried:
setupFiles
By predefining ENV in setupFiles, I could set ENV value and it works if I want to console.log(process.env.ADMINDB) in ../Device.ts. However, MONGODB_URI is a dynamic value which will not be known until a new MongoMemoryServer is created.
Top-level await
I tried to run async/await(get a new MongoMemoryServer and set the ENV) before import { DeviceFromDb1 } from '../Device' but doesn't help either.
I hope you can give me some hint if there is anyway to define the ENV before the module is loaded, or if I'm using the populate in a wrong way(I can get the result that I want by calling the endpoint so I assume this approach is ok)

Related

Behavior of export and export default in JavaScript

I am creating a NodeJS and Express app and want a module for configuration that runs once on start up then makes a serverConfig object available to any other module that needs these values. So far I have something like this:
const loadConfig = () => {
// logic to load configuration from environment variables, AWS Secrets Manager, etc.
}
export default loadConfig()
Now I can do
import serverConfig from 'config'
When I do this in multiple files will loadConfig() be called each time? Or will I have access to a reference to a single object from all of my imports? I only want this loadConfig() function to execute once on start up. These config values should not change during execution. And I don't want to repeat the (perhaps) time consuming logic in loadConfig() multiple times during each request to my API.
If not, what is the correct way to call loadConfig() once on server start up and create an object that all other modules can import and reference.
Everything in file scope executes once. Once the exports of a file are resolved, they are directly used by reference for any subsequent imports.
Any function call that is is not inside function itself will be executed exactly once when that file is imported when your program first starts up.
// a.ts
console.log('foo')
// b.ts
import './a'
// c.ts
import './a'
// main.ts
import './b'
import './c'
If you compile and run main.ts you will get exactly one output of "foo" to the console.
Given that, your example could be rewritten as:
const loadConfig = () => {
// logic to load configuration from environment variables, AWS Secrets Manager, etc.
}
const config = loadConfig()
export default config
Now its pretty clear that loadConfig() is called from the file scope and so only executes once.
This version:
export default loadConfig()
just avoids the intermediary variable but otherwise functions exactly the same.
If you did want to run that logic multiple times, you just need to export a function to do it for you.
// config.ts
export const loadConfig = () => {
// ...
}
// main.ts
import { loadConfig } from './config'
loadConfig()
loadConfig() // now do it again.
That doesn't sound like what you want, but I list it here to make it clear what the difference is.

How to mock only one constant exported from a config file with many constants using Jest

I have a config file with simplified content (it contains several more constants) like this:
export const appTitle = "Drzewo wyboru"
export const warnings ={
missing:" Kryterium"
duplicate: "Duplikacja"
In a test file I have written a mock like this:
jest.mock('../../../src/config',()=>({AppTitle:'abc123test'}));
The problem is that other items in the config file are necessary to correctly render the tested component as well - so this mock breaks a test.
I have read about possible uses of jest.requireActual but it works with objects and in config I have loose items.
How could I mock only this one item, leaving the rest intact without changing the structure of my config file?
You can do partial mocking for your module:
jest.mock('../../../src/config',() => {
const originalModule = jest.requireActual('../../../src/config');
return {
__esModule: true,
...originalModule,
AppTitle: 'abc123test',
};
});
More information can be found in the official doc here.
(Your example states that you want to mock out AppTitle, yet the actual module contains appTitle which is a different property, I am not sure if this was a mistake, but if it was I can update my answer)

In Jest: In setupTestFrameworkScriptFile, can I access the name/filename of the current executing test file

In many of my tests, I create a new slqite database and want to use the name of the test file as the sqlite DBname. This way, all my DB tests are isolated from each other.
As a related question: I would like my setupTestFrameworkScriptFile to be able to read config items (an Object) from the test. Is there a standard way to pass these configs from test to TestFramework?
Here is an example:
In myclass.test.js:
// "tell" the setup file I'm named 'myclass'
global.testId = function () {
return "myclass"
}
In setupTestFrameworkScriptFile:
beforeAll(async () => {
// if the test supports this method
if (typeof testId === 'undefined') {
return
}
// create an sqlite DB with that name
_createAndSyncDb(testId)
Instead I would like the code in setupTestFrameworkScriptFile to read the filename from the Jest environment, removing the need for the test and setupTestFrameworkScriptFile to both know about a previously agreed upon method named testId.

Require JSON as deep copy

I am writing tests right now for my node application.
I have fixtures which I use to test my data and I ran into the Problem, that when I alter any of them in a method, then they are globally altered for all the other tests as well, which obviously has to do with referencing. Now I figured if I write my fixtures into a JSON and require that JSON in each file then they will have unique references for each file, which turns out now, they don't.
My question would be: is there an easy way to handle fixtures in Node such that every file has an instance of the fixtures which won't affect the other test files.
The way I currently import my fixtures in every test file:
const {fixture1, someOtherFixture } = require('../../../../../fixtures/keywords.json');
require calls are cached, so once you call it, consecutive calls will return the same object.
You can do the following:
const {fixture1, someOtherFixture } = require('../../../../../fixtures/keywords.json');
const fixtureCopy = JSON.parse(JSON.stringify(fixture1));
const someOtherFixtureCopy = JSON.parse(JSON.stringify(someOtherFixtureCopy));
or use a package:
deepcopy
clone
const deepcopy = require('deepcopy');
const {fixture1, someOtherFixture } = require('../../../../../fixtures/keywords.json');
const fixtureCopy = deepcopy(fixture1);
const someOtherFixtureCopy = deepcopy(someOtherFixtureCopy);
Or change your module to export a function that will return new copies everytime. This is the recommended approach in my opinion.
module.exports = {
get() {
return deepcopy(fixture); // fixture being the Object you have
}
}
const fixture = require('./fixture');
const fixture1 = fixture.get();
This isn't specific to JSON. It's not uncommon that modules need to be re-evaluated in tests. require.cache can be modified in Node.js to affect how modules are cached, either directly or with helpers like decache.
Depending on the case,
decache('../../../../../fixtures/keywords.json')
goes before require in a test, or to afterEach to clean up.

Can I use a custom module resolution function (like "proxyquire") in place of require() with TypeScript?

I have a TypeScript file config.ts that will be run with node:
import myDependency = require('my-dependency');
export = {
doSomething = () => {
...
}
}
In other TypeScript file, I can import this file with full type safety:
import config = require('./config');
config.doSomething();
config.doSomethingElse(); // compiler error, this method doesn't exist
Now I want to unit test this script. In order to mock out the dependencies that this script require()s I'm using proxyquire, which lets me provide the values that my script will get when it makes calls to require(). Here's what my test might look like:
import proxyquire = require('proxyquire');
const config = proxyquire('./config', {
'my-dependency': {} // this mocked object will be provided when config.ts asks for `my-dependency`
});
expect(config.doSomething()).to.do.something();
This works fine, except that my config variable is of type any because I'm using proxyquire() in place of require(). TypeScript must give the require() function special treatment to allow it to perform module resolution. Is there a way to tell the TypeScript compiler that proxyquire() should also do module resolution, similar to require()?
I could rewrite config.ts as a class or make it use an interface. Then I would be able to explicitly type the variables in my tests by importing the class/interface definition. But allowing proxyquire() to implicitly type things for me would be far be the easier solution.
There is a workaround - you can get the type of config.ts module by importing actual module and using typeof in a type cast:
import proxyquire = require('proxyquire');
import configType = require('./config');
const config = <typeof configType> proxyquire('./config', {
'my-dependency': {} // this mocked object will be provided when config.ts asks for `my-dependency`
});
config.doSomething();
// config.noSuchMethod(); // does not compile
This is not ideal because you have to import the same module in your test twice - the real one just to get at the type of it and "proxiquired" one to actually use in your tests, and you have to be careful not to mix up the two. But it's pretty simple compared to the task of implementing another variant of module resolution for typescript. Also, when configType is used in this way - for typing only - its import will not even appear in generated javacsript code.

Resources