How to enable Intl in node for tests using Jest? - node.js

I am trying to test currency formatting functions and while they work in the browser, they are not working in my test environment. I understand that intl does not come standard with node, so I have added intl and full-icu to my devDependencies, but this has not helped
var IntlPolyfill = require('intl');
require('full-icu');
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
expect((4000).toLocaleString('fr-ca',{style: 'currency',currency: 'CAD',currencyDisplay: 'symbol'})).toBe('4 000,00 $')
/* test output:
Expected: "4 000,00 $"
Received: "CA$ 4,000.00"
*/
In IE11, Chrome and Firefox I get the expected result ("14 337,00 $") but in Node I am not.
I have found little help online, but did find reference to the 2 libraries I am using here. What do I need to do for the node environment used by Jest to have access to the proper Intl functions. The function is there, but seems to be returning the US formatting for every locale.

First comes my answer that immediately came into my mind and solves the problem, but it in fact is an anti-pattern - I simply leave it here so others can learn from it. The good solution follows further down in my answer:
You simply can import the node module intl into you Jest test and pass it to the JS you are testing:
import Intl from 'intl';
import CurrencyFormatter from '../../../js/modules/CurrencyFormatter.js';
describe('CurrencyFormatter', () => {
let formatter = null;
beforeEach(() => {
formatter = new CurrencyFormatter(Intl);
});
test([...]);
});
And in your JS you conditionally use the intl module if passed by Jest:
export default class CurrencyFormatter {
constructor(optionalIntl) {
// Needed for Jest, because it doesn't support Intl natively:
if (optionalIntl != null) {
global.Intl = optionalIntl;
}
[...]
}
[...]
}
This way you keep the superfluous intl module out of your production code and make it available to Jest for tests only. But as you can clearly see, this means adjusting the production code for the sole purpose of satisfying the test's needs. This is called an anti-pattern. The production code should always serve production needs (and readability/understandability for other devs, ofc), not the testing environment.
So for that reason you should rather follow the second solution, which adidtionally has the advantage of fixing the issue for all similar tests in the suite and of using much less code changes. In fact it does not change the code but only the configuration:
[Edit:]
As a colleague mentioned, this indeed is an anti-pattern - do not adapt your production code in order to pass the test. Yet, there is a better option, using the node module full-icu by passing it to node in the scripts entry for jest, i.e. do something like this in your package.json:
[...]
"scripts": {
"test": "node --icu-data-dir=node_modules/full-icu node_modules/jest/bin/jest.js --version --config=./jest.config.js"
},
[...]

Related

What is the CORRECT working flow for typescript, nodejs, npm, esmodule and commonjs?

I'm writing an idle game with typescript. It runs into a situation that, I should deal with some compute big number logic both on client and server. The client does not support bigint well, and the server does. So, for some lazy reason, I make a choice to cut out the same logic, caculate with JSBI(There 's babel plugin to convert JSBI to bigint), and export a single npm package. I think the client and the server will use it in there situation and it works.
So I have it done, and I got the problem. The client is es module, the server is commonjs. I ran babel on the whole package and got some compile problem -- some JSBI useages does not been converted because of functions was invoked deep and crossed. I made a new package called jsbi-extension, and change all JSBI function to call jsbi-extension instead.
It works after babel runs, but I got new problems.The package should export 2 different code with cjs and mjs extension, and after babel, Igot 2 different .d.ts . I don't know how to config the package.json to export different .d.ts with different target, and I think if I wan't to make it universal, maybe there should be 4 export for one single npm package. Like for bigint & cjs, bigint & mjs, JSBI & cjs, JSBI & mjs .. and I wanna to know If there's some CORRECT working flow for this situation.
The jsbi-extension is here https://github.com/darklinden/jsbi-extension and at last I build it to different branch to make it works, I think it is noy CORRECT.
Is there any Macro or any else, like type BI = ifdef bigint then bigint else (import JSBI) endif to define for .d.ts file?
Or Is there any key for package.json to define separated exports? like
{
"exports" : {
".":[
{
"importerHasImported" :"jsbi",
"import-types":"...",
"import":"...",
"require-types":"...",
"require":"..."
},
{
"importerNotImported" :"jsbi",
"import-types":"...",
"import":"...",
"require-types":"...",
"require":"..."
}
]
}
}

Using environment variables in Node and Jest

I have a NodeJS app. For a long time I've been using Mocha to run tests. I simply had a package.json like
"dev": "node index --env=dev",
"start": "node index",
"test": "mocha './test/**/*.spec' --env=dev"
Now I am switching to Jest for a variety of reasons, and the test script becomes simply
"test":"jest"
Within my test suites, I spin up an instance of my app. In functions throughout my app there are decisions based on the environment. Simplified example:
const ProcessArgs = require("minimist")(process.argv);
export function uploadImage(image){
if(ProcessArgs.env === "dev"){
return writeToLocalTestFolder(image);
}
else {
return uploadToCloud(image);
}
}
So this used to look at the --env=dev arg, but that's no longer there. (If I try to add --env=dev to Jest, well that's an entirely different issue). I do in fact properly use Heroku's secrets for my actual important env variables like API keys, etc, but I now seem to be stuck here on how I should tell Jest what env it is in, since it needs env=node to work properly.
Mocking out environment variables in Jest is a little bit more tricky as there really is no way to mock them out before the code that you want to import gets evaluated. However, there is a handy workaround. You can mock out the library that is contains the environment variables and have it return specifically what you need.
Referring to your example, you can mock out what environment variables are returned by minimist by using the following code (make sure it is used before the describe of your test suite:
jest.mock('minimist', () => {
return () => ({ env: 'dev'});
});
The if(ProcessArgs.env === "dev"){ conditional check should run as expected.

Can you test CDK constructs with Mocha instead of Jest?

The developer guide makes it sound like you can only use jest with the cdk. However, our projects currently use mocha. We could use jest for the cdk and keep everything else the same, but we're wondering if anyone has had any luck using mocha to test the cdk.
EDIT:
So far, it seems to have worked with this simple set up:
installed ts-node, mocha
ran with mocha -r ts-node/register file.test.ts
file.test.ts:
import { expect as expectCDK, matchTemplate, MatchStyle } from '#aws-cdk/assert';
import * as cdk from '#aws-cdk/core';
import * as Infrastructure from '../lib/infrastructure-stack';
it('Empty Stack', () => {
const app = new cdk.App();
// WHEN
const stack = new Infrastructure.InfrastructureStack(app, 'MyTestStack');
// THEN
expectCDK(stack).to(matchTemplate({
"Resources": {}
}, MatchStyle.EXACT))
});
Has anyone tried more complicated tests?
I believe the cdk assert library was written with jest in mind but I couldn't find any classes that will only work with jest so it should work with any test framework since they provide their own expectations function, cdkExpect

Error mocking a "non-strict" legacy module on Jest

I have some really messy non-strict-compliant legacy code on javascript, running just fine on NodeJs 12, and I'm trying to abstract it away and test the overlaying, new layers of code using Jest/Mocks.
But when I try to run the tests I receive the following error:
Test suite failed to run
SyntaxError: /legacy-path/messy_legacy_code.js: Legacy octal literals are not allowed in strict mode (557:66)
at Parser._raise (node_modules/#babel/parser/src/parser/error.js:60:45)
I'm trying to mock it away first thing on my test code, but still get this error. It seems that Jest is trying to parse it with Babel; it really won't find any compliant code there... It just runs on Node, nothing else.
I already tried mocking the legacy code itself and also tried making a container to "abstract" it away and mocking the container. But it seems Jest still tries to read every bit of noncompliant code behind it.
My modern.test.js code looks like this:
jest.mock('../../../../legacy-path/messy-container')
const { ModernLayer } = require('../../../../modern-path/modern-module');
Any ideas on how I cant completely block Jest from trying to read this noncompliant code and just mock it away?
jest.mock('...') performs auto-mock, unless a mock from __mocks__ is used. It tries to process module exports and fails on syntax error.
The module should be manually mocked instead:
jest.mock('../../../../legacy-path/messy-container', () => ({ ModernLayer: ... }));

Jest Mock Globally From a Node Module

I am writing a series of Node modules that require a bunch of common Jest mocks to be setup before I can write unit tests.
I am trying to refactor the unit test setup into a separate module so I can avoid rewriting the setup each time.
The problem is that when I import the following code as a module it no longer mocks the other libraries, while it works just fine when the mocks are set up in a utility file.
Working Code:
jest.mock('#actions/core')
jest.mock('#actions/github')
const { GitHub, context} = require('#actions/github')
const core = require('#actions/core')
GitHub.mockImplementation(() => {
return {
{
repos: {
getContents: jest.fn()
}
}
}
}
module.exports = { core, GitHub, context }
I keep this in a utils.js file next to my test files and import it like const { core, GitHub, context } = require('./utils.js') and everything mocks as I expect. I can run expect().toHaveBeenCalledTimes() and get the numbers I expect.
The problem appears when I move utils.js to another module and require it.
I know that at bottom of the jest documentation it says "...Another file that imports the module will get the original implementation even if it runs after the test file that mocks the module." But I am seeing this work inconsistently with the external file.
Anyone know how to get mocking setup in external modules?

Resources