Webpack handlebars-loader temaplate file does not resolve in mocha unit test - node.js

I am using handlebars-loader to use handlebar templates with webpack.
I have the following code structure. I am able to build code using webpack but when I run mocha unit tests the handlebar template fails to resolve.
webpack.config.js
const path = require('path');
module.exports = (env) => {
// Rest of webpack.config.js
module: {
rules: [
{ test: /\.handlebars$/, loader: "handlebars-loader" }
]
};
};
index.ts
import EmailTemplate from './templates/email.handlebars';
export const handler = async (event, _context) => {
let emailResponse = EmailTemplate({ code: '123' });
return emailResponse;
};
Error when running unit tests
Cannot find module './templates/email.handlebars' or its corresponding type declarations.
import EmailTemplate from './templates/email.handlebars';

I was able to run unit test after requiring handlebars-loader while running unit test command.
Also converted import to require while using handlebars templates in code.
index.ts
const EmailTemplate = require('./templates/email.handlebars');
export const handler = async (event, _context) => {
let emailResponse = EmailTemplate({ code: '123' });
return emailResponse;
};
And while running mocha test added --require handlebars-loader
node_modules/.bin/_mocha --require ts-node/register --require handlebars-loader functionName/test/**/*.spec.ts

Related

Is it possible to collect coverage on code loaded with vm.runInContext with Jest?

I'm working on a legacy JS project which is not using any require/import. When deploying, the files are just concatenated and the result is sent to a server.
In order to write tests with jest, I created a custom environment to load all the JS files in the global context so that I can call the functions in the test file.
For example:
src/index.js
function sum(x, y) {
return x + y;
}
src/index.spec.js
it('should sum two numbers', () => {
expect(sum(1, 2)).toBe(3);
});
jest.config.js
module.exports = {
clearMocks: true,
collectCoverage: true,
collectCoverageFrom: [
"src/**/*.js",
],
coverageDirectory: "coverage",
coverageProvider: "v8",
testEnvironment: "./jest.env.js",
};
jest.env.js
const NodeEnvironment = require('jest-environment-node').TestEnvironment;
const fs = require('fs');
const vm = require("vm");
const path = require("path");
class CustomEnv extends NodeEnvironment {
constructor(config, context) {
super(config, context);
this.loadContext();
}
loadContext() {
const js = fs.readFileSync('./src/index.js', 'utf8');
const context = vm.createContext(this.global);
vm.runInContext(js, context, {
filename: path.resolve('./src/index.js'),
displayErrors: true,
});
Object.assign(this.global, context);
}
}
module.exports = CustomEnv;
When I run npx jest, the test is executed but the coverage is empty...
Any idea on how to fix the coverage?
I've created a minimal reproducible repo here: https://github.com/GP4cK/jest-coverage-run-in-context/tree/main. You can just clone it, run npm i and npm t.
Note: I'm happy to change v8 to babel or load the context differently if it makes it easier.

Jest: test imported constant on both NODE_ENV being production and test

I'm using jest, and I need to test something on both production and non-production environment. My code is importing a const variable, which has different value in prod/non-prod.
I found out the imported const is set when jest is started, before test is running. And I cannot change it when the test is running.
Is there a way to test the imported const on both prod/non-prod? please help :)
Here's a minimum example:
// foo.js
export const foo =
process.env.NODE_ENV === "production" ? "production" : "non-production";
// foo.test.js
import { foo } from "./foo";
describe("foo", () => {
it("should have value 'non-production' on test env", () => {
expect(foo).toBe("non-production"); // ✅ this works
});
})
// foo.production.test.js
import { foo } from "./foo";
describe("foo", () => {
it("should have value 'production' on production env", () => {
process.env.NODE_ENV = "production";
expect(foo).toBe("production"); // ❌ this fails as foo is 'non-production'
});
});
Here's a codesandbox with the above example: https://codesandbox.io/s/jest-test-forked-ce1xem
What I have thought of but didn't work out:
setting a different jest.config.js for foo.production.test.js. This didn't work out because I'm running test in nx monorepo, and nx requires me to specify a single path to jest config file
using require instead of import. This works but it seems fragile.

RequireJS + Mocha + JSDom + Node -> Shim config not supported in Node

I'm trying to setup a Mocha testing framework using JSDom with RequireJS. Because I'm running the test on node instead of using a browser (since I'm using JSDom), all the non AMD modules doesn't seem to be imported and is throwing Shim config not supported in Node. Does anyone know how I can export those modules to AMD or what the right approach is? (aka what I'm doing wrong)
Example of my set-up
Component.js
define(["jquery", "non_AMD_Module", ... ], function($, NonAMDModule, ...) {
let component = {
...
foo = () => {
NonAMDModule.bar();
};
};
return component;
});
Component.test.js
const requirejs = require('requirejs');
const { JSDOM } = require('jsdom');
requirejs.config({
baseUrl: "dist/app",
paths: {
jquery: "lib/jquery",
component: "path_to_component",
non_AMD_Module: "path_to_module"
},
shim: {
non_AMD_Module: { exports: "non_AMD_Module" } // This doesn't work
}
});
const { window } = new JSDOM("<html></html>");
global.window = window;
global.document = window.document;
global.$ = requirejs('jquery');
const Component = requireJS('component');
describe('test', () => {
it('is a simple test', () => {
const testComponent = new Component();
testComponent.foo();
}
});
When I run the test suite, I get:
Mocha Exploded!
TypeError: Cannot read property 'bar' of undefined
running r.js -convert "path_to_module" did not work for this module
Looking at the source code for jQuery, I found that there's this boiler-plate coded that exports it to AMD.
This can be added at the bottom of the non-AMD-module in order to export it to an AMD module accessible by RequireJS
if ( typeof define === "function" && define.amd ) {
define([], function {
return non_AMD_Module;
});
}
Other Resources:
Shim a module in Require.js that uses module.exports possible?
https://github.com/requirejs/requirejs/wiki/Updating-existing-libraries#anon

Re-run Jest when jest.config changes

I am working with a jests.config.js file:
// jest.config.js
module.exports = {
verbose: true,
setupFiles: ["./helpers/setup/environment.js"],
testMatch: ["**/__tests__/v2/tests.cloud.js"],
globals: {
_: true,
},
watchPathIgnorePatterns: ["./src/dataset", "./logs"],
};
I run Jest with watch:
jest --watch
In order to develop each test on its own I change the test file on testMatch every time I move on to the next test I am writing. Is there a way for the watch to reload the Jest config itself when the configuration file changes?
There are plenty other options
CLI [TestPathPattern]
if you run jest --help you'll see you can pass TestPathPattern to match test files
$ jest --help
Usage: jest.js [--config=<pathToConfigFile>] [TestPathPattern]
--onlyChanged, -o
Jest will attempt to run only tests related to the changed (in the current repository) files.
watch mode (p)
While in --watch mode you can press P and enter regex to select which file to run
Ended up writing a short NodeJS script:
const fs = require("fs");
const { spawn } = require("child_process");
const filename = "../jest.config.js";
let jest;
const run = () => {
if (jest) {
jest.kill();
}
jest = spawn("jest", ["--watch"], { stdio: "inherit" });
};
run();
fs.watch(filename, run);
process.on("SIGINT", function() {
console.log("Caught interrupt signal");
process.exit();
});

Jest mocking module

I am writing unit tests for a node application using Jest.
The Node code is using a third party library to log information.
The library has a function getLogger which you should call to return a logger object.
I am trying to mock the calls for that library and detect its calls in my unit test.
The node code is as follows:
const logger = require('third-party-libary').getLogger('myModule')
....
function submitSomething() {
....
logger.info('log something')
}
In my Jest unit test, I tried to mock those logger calls in many different ways, with no success, and always come back as "logger is not defined"
I tried:
jest.mock('third-party-library');
const loggerFactory = require('third-party-library');
const logger = {
error: jest.fn(),
info: jest.fn()
};
loggerFactory.getLogger.mockImplementation(() => logger);
But it always return error :
cannot find "info" for null object
I tried this as well:
jest.mock('third-party-library')
const loggerFactory = require('third-party-library');
const logger = {
error: jest.fn(),
info: jest.fn()
};
loggerFactory.getLogger = () => logger
I tried this:
jest.mock('third-party-library')
const loggerFactory = require('third-party-library');
const logger = {
error: jest.fn(),
info: jest.fn()
};
loggerFactory.getLogger = jest.fn(() => logger)
With the same error
I switched between the jest.mock to make it after the require, with no luck
Your approach works fine, just note that your code creates logger as soon as it runs so the mock for getLogger has to be in place before the code is required:
jest.mock('third-party-library');
const loggerFactory = require('third-party-library');
const logger = {
error: jest.fn(),
info: jest.fn()
};
// const { submitSomething } = require('./code'); <= would NOT work here
loggerFactory.getLogger.mockReturnValue(logger);
const { submitSomething } = require('./code'); // <= works here
test('submitSomething', () => {
submitSomething();
expect(logger.info).toHaveBeenCalledWith('log something'); // Success!
});

Resources