Jest causing a module to disappear - javascript

I'm trying to write tests for an old codebase of mine. It uses the isbn package from NPM, which is tiny but does the trick. But whenever I write tests that involve the module, it disappears. That is to say - the value of the module is set to {}.
I've written up two files to try and isolate this issue. I just can't figure out how this test is failing.
First file, isbnTest.js:
const { ISBN } = require("isbn");
function isbnExists() {
return ISBN !== undefined;
}
console.log(isbnExists());
module.exports = {
isbnExists,
};
Pretty simple:
import the module,
one function that just checks if the module has been successfully imported,
log the results, and
export the tester function.
Running this file from the console logs
true
But what happens when we run this code from within Jest?
The second file, ./isbnTest.test.js:
const { isbnExists } = require("./isbnTest.js");
test("isbn should exist", () => {
expect(isbnExists()).toBe(true);
});
When I run npm test with these two files, the test fails.
FAIL
./isbnTest.test.js
✕ isbn should exist (4ms)
● isbn should exist
expect(received).toBe(expected) // Object.is equality
Expected: true
Received: false
2 |
3 | test("isbn should exist", () => {
> 4 | expect(isbnExists()).toBe(true);
| ^
5 | });
6 |
at Object.<anonymous> (isbnTest.test.js:4:24)
console.log isbnTest.js:7
false
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
It seems as though Jest must be doing some custom importing stuff that's somehow missing this module. Almost like it's being replaced with an empty mock? But I really don't know.
I'd love to remove the isbn package from my program, but without any tests I don't feel confident that I can ensure I won't break anything.
EDIT:
A commenter has pointed out that this doesn't reproduce, which means there's something askew on my machine. Can anyone provide as guess as to what that might be? Deleting and reinstalling the NPM modules doesn't do the trick. I don't think I have any jest config files.
After some further investigation, the problem is being caused by this line in the module:
var exports = typeof window === 'object' && window ? window: module.exports;
If I comment that out, Jest picks it up fine.

Related

"istanbul ignore next" command seems to be ignored

I'm trying to use Jest to test my code. It was working quite fine until I tried to exclude a class method from the tests.
The querySelector() call is the reason why I want to skip this method from being tested ("document" is obviously null unless I run the script in the browser)
I tried this solution, which seems to be the most suggested one:
/* istanbul ignore next */
appendNewInputFields() {
const howMany = Number(document.querySelector('#items-to-add').value);
[...Array(howMany)].forEach( i => {
const newInputField = this.createNewItemInputField();
this.inputItemsContainerNode.append(newInputField);
});
}
But the test keeps failing and the line /* istanbul ignore next */ seems to be ignored.
I've also tried putting the comment between the function signature and its body (as was suggested somewhere here on SO), but no luck:
FAIL js/DOMManager.test.js
● Test suite failed to run
TypeError: Cannot read property 'value' of null
135 | appendNewInputFields() /* istanbul ignore next */ {
136 |
> 137 | const howMany = Number(document.querySelector('#items-to-add').value);
| ^
138 |
I've read around that this might be related to babel-plugin-istanbul. I've tried
npm --save-dev uninstall babel-plugin-istanbul
which in the terminal returned:
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents#2.3.2 (node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents#2.3.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
removed 18 packages and audited 521 packages in 2.178s
but the folder babel-plugin-istanbul is still present in /node_modules/ inside my working folder and the test keeps failing as if nothing changed.
If I comment out the body of the function, the other test suites work perfectly. If I try to apply the ignore next command to any other part of the code, the tests pass just fine and the line is completely ignored.
If I try to manually delete the /babel-plugin-istanbul/ folder (from the /node_modules/ in my working folder), Jest stops working.
(This is the first time I installed Node.js, and I did it only because I wanted to start unit testing with Jest. I'm pointing this out because these are my first steps venturing out of the vanilla world. I don't know how to deal with Node.js nor npm, I just launched a couple commands to install it, I wrote a few tests for Jest and they all immediately worked fine. I'm not using any other framework, I'm trying to stick to vanilla JS as much as possible.)
----------------------- Edit:
I tried changing the code to this:
appendNewInputFields() {
// TODO solve the istanbul ignore issue
let howMany;
/* istanbul ignore if */
if(document != null) {
howMany = Number(document.querySelector('#items-to-add').value);
[...Array(howMany)].forEach( i => {
const newInputField = this.createNewItemInputField();
this.inputItemsContainerNode.append(newInputField);
});
console.log("added " + howMany + " input fields");
}
}
I keep getting
FAIL js/DOMManager.test.js
● Test suite failed to run
TypeError: Cannot read property 'value' of null
139 | /* istanbul ignore if */
140 | if(document != null) {
> 141 | howMany = Number(document.querySelector('#items-to-add').value);
| ^
142 |
143 | [...Array(howMany)].forEach( i => {
144 | const newInputField = this.createNewItemInputField();
I'm totally clueless at this point. Given the conditional, "document" should be null and that block entirely skipped, but it keeps failing the test.
The same code works exactly as intended when run in the browser.
So I made a few tests in a blank new folder, and it finally struck me.
Apparently I had misunderstood the purpose of /* istanbul ignore next */.
Its function is not to skip code from being executed during the tests, but rather prevent that portion of code to be taken into account when determining the amount of total code that has been tested. The code runs (if there is anything calling that function), but those lines just don't count when Jest sums up the amount of lines it tested (which is the purpose of --coverage, I guess). If an error occurs, it is thrown as it would normally be.
My problem wasn't really related to Jest nor the istanbul package. I feel pretty dumb realizing this now, but what I needed was just proper exception handling.
I implemented a few old fashioned try/catch blocks: now everything is tested smoothly and the istanbul ignore directive correctly behaves as expected (now that I know what to expect, that is): functions that are not tested and flagged to be ignored, they just don't appear in the final coverage report.
I hope this helps anybody who might stumble in my same misunderstanding.
Of course, if anybody more competent than me can confirm this interpretation or has any suggestion or further explanation, it would obviously be much appreciated.
I was with the same error with window.alert command with jest:
ReferenceError: alert is not defined
4 |
5 | /* istanbul ignore next */
> 6 | alert(helloWorld());
| ^
7 |
so I tried to use try-catch and SUCCESS!
before:
alert(helloWorld());
after:
const alerta = () => {
try {
return alert(helloWorld());
} catch (error) {
return false;
}
}
testes:
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
This might also happen if your bundling tool (esbuild, for instance) removes comments, therefore istanbul does not see them.
If that's the case, you might want to change your bundling tool to something like terser (make sure that comments are not omitted there, too) or adjust minification options of the bundling tool you use.
This is how it would look like if you use Vite:
build: {
minify: 'terser',
terserOptions: {
format: {
comments: 'all', // or regular expression /istanbul\signore\s/
},
},
},

Jest gives "Your test suite must contain at least one test" when certain file is imported even though there is a valid test

Whenever I import a certain file from my test file, Jest produces an error that I do not have any tests in the test file, even though there is one with just constants that should pass.
This is running inside a create-react-app folder, but I am using a global install of Jest for the server-side testing.
The Error
FAIL server/__tests__/server.test.js
● Test suite failed to run
Your test suite must contain at least one test.
at onResult (../../../../usr/lib/node_modules/jest/node_modules/#jest/core/build/TestScheduler.js:173:18)
A simplified version of the test file:
const timer = require("./timer.js");
test("should pass", () => {
expect(1 + 2).toBe(3);
});
A simplified version of timer.js:
class Timer {
constructor(ms, onCompleteFunction) {
this.ms = ms;
this.startTime = Date.now();
setTimeout(onCompleteFunction, ms);
}
}
test = () => {
let timer = new Timer(5000, () => console.log("Test Done!"));
};
test();
module.exports = Timer;
(This is a self-answer)
Before adding real testing with Jest, in the Timer.js file I had added a quick test function, just to verify that it was working without running the whole app.
Unfortunately, the function, named test, was not prefixed with var, let, or const. From what I can tell, it overrode the test function from the Jest library, and this error message was the result. When test = 1 or similar was present, the error was
TypeError: test is not a function
This is an extreme edge case due to poor coding practices and something I would have thought was a syntax error on my part, so I don't fault the error message for being unhelpful. I hope this can help someone in the future anyways.

Running a test with Mocha also launches the main program

I'm trying to use Mocha to test a CLI app. The tests are running fine but, when I launch the testing procedure, it also launches the main app:
$ npm run test
> standardize-js#0.2.2 test C:\Users\Gaspard\Documents\Code\standardize-js
> mocha "./source/**/*.spec.js"
? Choose your project language or framework (Use arrow keys) //<-- THIS IS THE PROGRAM
> Javascript
Typescript
AngularJS
Main function //<-- THIS IS THE TEST
ask if the configuration is valid
Configuration is not valid, terminating program.
√ should return false if the configuration is not accepted
1 passing (29ms)
I'm kind of new to the testing world and I'm really struggling to understand what I'm doing wrong.
Here is the NPM script used to launch mocha :
"test": "mocha \"./source/**/*.spec.js\""
Here is my testing method:
/* eslint-disable func-names */
const { expect } = require("chai");
const main = require("./index").test;
describe("Main function", function() {
describe("ask if the configuration is valid", function() {
it("should return false if the configuration is not accepted", function() {
const fakeAnswer = false;
expect(main.validateConfiguration(fakeAnswer)).to.equal(false);
});
});
});
And here is my index.js file:
function validateConfiguration(answer) {
if (answer === false) {
console.log(chalk.red("Configuration is not valid, terminating program."));
return false;
}
return true;
}
const run = async () => {
//MAIN FUNCTION
};
run();
// Export functions and variables to be able to test
exports.test = {
validateConfiguration
};
It's not a problem with mocha. It is simply now node.js modules work.
When you do this:
const main = require("./index").test;
Node.js will execute index.js and then check the value of module.exports. If the module (index.js) sets or modifies module.exports then node will export it for use by require(). But note, in order for node to know that the module has exported anything it must execute the javascript file.
Node.js does not have any ability to parse and analyze javascript syntax (that's V8's job). Unlike other languages such as C or Java, modules in node.js are not implemented at the syntax level. Therefore the javascript language does not need to be modified (eg. ES6 modules) for node.js to support modules. Modules are simply implemented as a design pattern.
In your index.js file you call run:
run();
When require() loads index.js it will therefore also cause run() to be called.
Test libraries, not main
The solution to this is to implement your own logic as modules and test that, not test index.js:
mylib.js:
function validateConfiguration(answer) {
if (answer === false) {
console.log(chalk.red("Configuration is not valid, terminating program."));
return false;
}
return true;
}
// Export functions and variables to be able to test
exports.test = { validateConfiguration };
index.js:
const validateConfiguration = require("./mylib").test;
const run = async () => {
//MAIN FUNCTION
};
run();
You can now use your test script as written.
How can you not test code??
The strategy to keep index.js bug free without testing is to remove all logic from it except for the minimum amount of code to wire all your other code up together to run the app. The code should be as simple as "Hello World". That way, the code in main is so small and so simple that you can test it for bugs using your eyeballs.
Any code in index.js that causes a bug should be refactored into its own library so that it can be tested separately. There are a small handful of corner cases, such as loading environment variables or opening port 80 where you can't really separate into a library because they literally are wiring logic. For such cases you just have to be really careful.
It's calling run because you are telling it to right after defining the method.

How to mutate node-config in memory

I'm trying to mutate the value of my config in memory for testing, I've tried adding process.env.ALLOW_CONFIG_MUTATIONS=true in several spots in the application, as well as through the command line and my .env file.
The config.util.getEnv('ALLOW_CONFIG_MUTATION') method always returns undefined.
I've also tried using importFresh and MockRequest as per examples I've seen online, neither of which allow me to mutate the config in memory, and then reset the value later.
Does anyone have any idea about this?
Update: here's an example of what I'm trying to accomplish
const config = require (config);
const app = new App(config)
it(`does a thing with base config`, () => { ... }
it('does a thing with modified config, () => {
// here i would need to modify my config value and
// have it change the original config that's currently in
// application memory
config = newConfig
expect(config.get('newValues')).to.equal(true)
}
Thanks!
If it is the same config module that I have used (I think I is) then add a custom-environment-variables.js OR test.js with you test config.
test.js will need an ENV=test to work and the custom-environment-variables need something like (for Mac's and NPM) $ npm run funcTest -> yarn serverRunning && NODE_ENV=test wdio wdio.conf.js.
the JSON will look something like
{
test: 'Value'
}

node-qunit doesn't include my source files in scope

I've installed node-qunit (stable) from npm, but can't seem to get any tests working. My source files don't seem to be included in scope.
./source/myscript.js:
var myObj = {
a : true
}
./test/tests.js:
test("that a is true", function () {
ok(myObj.a);
});
./test/runner.js:
var runner = require('qunit');
runner.run({
code : './source/myscript.js',
tests : './test/tests.js'
});
./Makefile:
test :
<tab>node ./test/testrunner.js
.PHONY: install test
If I run make test, I get a 'ReferenceError: myObj is not defined' error. The source file does run, because it can throw errors. It just doesn't seem to be included in the global scope as it should. It doesn't work if I do it from the command line, as per the instructions in the node-qunit readme. Anyone have any idea how to get this working?
You're not exporting anything. Behind the scenes, node-qunit is using require to load the specified modules. To expose variables when a module is required, you have to add them to the exports object (or assign your own object to the exports variable)
(There's also a syntax error - ; in the object literal)
This works for me:
./source/myscript.js:
exports.myObj = {
a: true
}
./test/tests.js:
QUnit.module('tests')
test("that a is true", function () {
ok(myObj.a)
})
./test/runner.js:
var runner = require('qunit')
runner.run({
code : './source/myscript.js'
, tests : './test/tests.js'
})

Categories