"Cannot use import statement outside a module" got message when I try to mocha - node.js

I was using jest now on vue/nuxt.
And I am trying to change TDD by mocha and chai.
Installed mocha though npm i mocha --save-dev
package.json
"scripts": {
"test": "mocha"
},
test/list.spec.json
import { expect } from 'chai';
describe('test', function () {
it('should be rendered', function () {
const comp = [1, 2, 3];
expect(Math.max(...comp)).to.above(1);
});
});
Error message when I typed npm test
import { expect } from 'chai';
^^^^^^
SyntaxError: Cannot use import statement outside a module
What is problem with that ?

You cannot use the import syntax in a Node app (import { expect } from 'chai';) if your project isn't properly configured to use this newer syntax. The original Node syntax pre JS modules is const variable = require('./folder/file')
You have to set up Jest or the project you work on to work with ES6 Modules. In package.json, add "type": "module". Here's an explanation: https://www.geeksforgeeks.org/how-to-use-an-es6-import-in-node-js/

Related

How to mock electron when running jest with #kayahr/jest-electron-runner

Setup
I want to unit test my electron app with jest. For this I have the following setup:
I use jest and use #kayahr/jest-electron-runner to run it with electron instead of node. Additionally, since it is a typescript project, I use ts-jest.
My jest.config.js looks like this:
module.exports = {
collectCoverage: true,
coverageDirectory: 'coverage',
coverageProvider: 'v8',
preset: 'ts-jest',
runner: '#kayahr/jest-electron-runner/main',
testEnvironment: 'node',
};
The test is expected to run in the main process. I have reduced my code to the following example function:
import { app } from 'electron';
export function bar() {
console.log('in bar', app); //this is undefined when mocked, but I have a real module if not mocked
const baz = app.getAppPath();
return baz;
}
The test file:
import electron1 from 'electron';
import { bar } from '../src/main/foo';
console.log('in test', electron1); //this is undefined in the test file after import
// jest.mock('electron1'); -> this does just nothing, still undefined
const electron = require('electron');
console.log('in test after require', electron); //I have something here yay
jest.mock('electron'); //-> but if I comment this in -> it is {} but no mock at all
it('should mock app', () => {
bar();
expect(electron.app).toBeCalled();
});
What do I want to do?
I want to mock electron.app with jest to look whether it was called or not.
What is the problem?
Mocking electron does not work. In contrast to other modules like fs-extra where jest.mock() behaves as expected.
I don't understand the behavior happening here:
Importing "electron" via import in the file containing the tests (not the file to be tested!) does not work (other modules work well), but require("electron") does.
I do have the electron module if not mocked in bar(), but after mocking not
while jest.mock("fs-extra") works, after jest.mock("electron") electron is only an empty object, not a mock
I would really like to understand what I did wrong or what the problem is. Switching back to #jest-runner/electron does not seem to be an option, since it is not maintained anymore. Also I don't know if this is even the root of the problem.
Has anyone seen this behavior before and can give me a hint where to start searching?

How to launch a chai test in node?

I have a node/typescript application. I'm trying to test routes with chai and chai-http. I can launch a file when I write an explicit name: yarn test myroute.test.ts, but yarn test at the root does nothing.
Also, the test is never performed. I only receive a Done in 0.06s. in my terminal. No "passed" or "failed".
Here is the test:
import chai from "chai";
import chaiHttp from "chai-http";
import { server } from "../index";
chai.use(chaiHttp);
const chaiApi = chai.request(server);
describe("GET /user/:id", () => {
it("return user information", async () => {
const res = await chaiApi
.get("/user/123")
.set("Cookie", "_id=567;locale=en");
chai.assert.equal(res.status, 200);
});
});
the package.json script is: "test": "test". I assume it's completely wrong, but chai doc says nothing about what to write here.
chai is an assertion library, similar to Node's built-in assert. It does NOT contain a test runner. You need to use mocha to collect and run your declared test suites and test cases.
After adding the mocha test framework, you can declare npm test script like this:
"test": "mocha --require ts-node/register --timeout 5000",
In order to run the TS test file, we need to specify ts-node/register. Use it like this:
yarn test myroute.test.ts

Stub an export from a native ES Module without babel

I'm using AVA + sinon to build my unit test. Since I need ES6 modules and I don't like babel, I'm using mjs files all over my project, including the test files. I use "--experimental-modules" argument to start my project and I use "esm" package in the test. The following is my ava config and the test code.
"ava": {
"require": [
"esm"
],
"babel": false,
"extensions": [
"mjs"
]
},
// test.mjs
import test from 'ava';
import sinon from 'sinon';
import { receiver } from '../src/receiver';
import * as factory from '../src/factory';
test('pipeline get called', async t => {
const stub_factory = sinon.stub(factory, 'backbone_factory');
t.pass();
});
But I get the error message:
TypeError {
message: 'ES Modules cannot be stubbed',
}
How can I stub an ES6 module without babel?
According to John-David Dalton, the creator of the esm package, it is only possible to mutate the namespaces of *.js files - *.mjs files are locked down.
That means Sinon (and all other software) is not able to stub these modules - exactly as the error message points out. There are two ways to fix the issue here:
Just rename the files' extension to .js to make the exports mutable. This is the least invasive, as the mutableNamespace option is on by default for esm. This only applies when you use the esm loader, of course.
Use a dedicated module loader that proxies all the imports and replaces them with one of your liking.
The tech stack agnostic terminology for option 2 is a link seam - essentially replacing Node's default module loader. Usually one could use Quibble, ESMock, proxyquire or rewire, meaning the test above would look something like this when using Proxyquire:
// assuming that `receiver` uses `factory` internally
// comment out the import - we'll use proxyquire
// import * as factory from '../src/factory';
// import { receiver } from '../src/receiver';
const factory = { backbone_factory: sinon.stub() };
const receiver = proxyquire('../src/receiver', { './factory' : factory });
Modifying the proxyquire example to use Quibble or ESMock (both supports ESM natively) should be trivial.
Sinon needs to evolve with the times or be left behind (ESM is becoming defacto now with Node 12) as it is turning out to be a giant pain to use due to its many limitations.
This article provides a workaround (actually 4, but I only found 1 to be acceptable). In my case, I was exporting functions from a module directly and getting this error: ES Modules cannot be stubbed
export function abc() {
}
The solution was to put the functions into a class and export that instead:
export class Utils {
abc() {
}
}
notice that the function keyword is removed in the method syntax.
Happy Coding - hope Sinon makes it in the long run, but it's not looking good given its excessive rigidity.
Sticking with the questions Headline „Stub an export from a native ES Module without babel“ here's my take, using mocha and esmock:
(credits: certainly #oligofren brought me on the right path…)
package.json:
"scripts": {
...
"test": "mocha --loader=esmock",
"devDependencies": {
"esmock": "^2.1.0",
"mocha": "^10.2.0",
TestDad.js (a class)
import { sonBar } from './testSon.js'
export default class TestDad {
constructor() {
console.log(purple('constructing TestDad, calling...'))
sonBar()
}
}
testSon.js (a 'util' library)
export const sonFoo = () => {
console.log(`Original Son 'foo' and here, my brother... `)
sonBar()
}
export const sonBar = () => {
console.log(`Original Son bar`)
}
export default { sonFoo, sonBar }
esmockTest.js
import esmock from 'esmock'
describe.only(autoSuiteName(import.meta.url),
() => {
it('Test 1', async() => {
const TestDad = await esmock('../src/commands/TestDad.js', {
'../src/commands/testSon.js': {
sonBar: () => { console.log('STEPSON Bar') }
}
})
// eslint-disable-next-line no-new
new TestDad()
})
it('Test 2', async() => {
const testSon = await esmock('../src/commands/testSon.js')
testSon.sonBar = () => { console.log('ANOTHER STEPSON Bar') }
testSon.sonFoo() // still original
testSon.sonBar() // different now
})
})
autoSuiteName(import.meta.url)
regarding Test1
working nicely, import bended as desired.
regarding Test1
Bending a single function to do something else is not a problem.
(but then there is not much test value in calling your very own function you just defined, is there?)
Enclosed function calls within the module (i.e. from sonFoo to sonBar) remain what they are, they are indeed a closure, still pointing to the prior function
Btw also tested that: No better results with sinon.callsFake() (would have been surprising if there was…)

Mock dependencies with Mocha and Typescript

I have a typescript project which uses mocha. Let's say we have two modules as follows.
// http.ts
export class Http {
}
// app.ts
import Http from './http';
export class App {
}
How could I mock the Http module when I'm testing the App?
The test is executed through the npm script as below.
"test": "cross-env NODE_ENV=test ./node_modules/mocha/bin/mocha",
And the mocha options (mocha.opts) looks like below.
test/setup.ts
--compilers ts:ts-node/register
--compilers tsx:ts-node/register
./src/**/*.spec.ts
ts-mock-imports gives you run time control over your imports and maintains type safety.
in app.spec.ts
import * as httpModule from 'http';
import { App } from '../src/app';
import { ImportMock } from 'ts-mock-imports';
const httpMock = ImportMock.mockClass(httpModule, 'Http');
const app = new App(); // App now uses a fake version of the Http class
Now you can control how the Http module behaves in your tests.
Mock the response to a get:
httpMock.mock('get', { data: true });
const response = app.makeGetRequest(); // returns { data: true }
The import statement in typescript is compiled to require. You can use proxyquire to mock any dependencies in your tests

Bootstrapping a Mocha test suite

I have numerous tests spread across multiple files in a Node JS application. I'd like to run bootstrap code prior to Mocha's execution of any of the test files. This is so that I can, for example, set globals to be used in each of the actual tests.
Sample bootstrap code
global.chai = require('chai');
global.expect = chai.expect;
global.sinon = require('sinon');
It seems Mocha loads all files under /test alphabetically, so if I name this bootstrap code "bootstrap.js" and everything else with a starting letter after "B" it "works".
Obviously this is fragile and sucky, but I don't want to put this boilerplate requiring of my supporting libraries at the top of every test file.
How do I tell Mocha to load a bootstrap script first, or create something functionally equivalent?
have you tried mocha --require mymodule.js TESTS_DIR
from the documentation
-r, --require
The --require option is useful for libraries such as should.js, so you
may simply --require should instead of manually invoking
require('should') within each test file. Note that this works well for
should as it augments Object.prototype, however if you wish to access
a module's exports you will have to require them, for example var
should = require('should').
you could also write at the top of each test to load the require("./bootstrap.js") and run tests.
I use mocha's flag --delay
If you need to perform asynchronous operations before any of your
suites are run, you may delay the root suite. Simply run Mocha with
the --delay flag. This will provide a special function, run(), in the
global context.
setTimeout(function() {
// do some setup
describe('my suite', function() {
// ...
});
run();
}, 5000);
If prior to running Mocha you want to first run some bootstrap code in a file that uses ECMAScript 2015 Module syntax (i.e. import instead of require)
Create the following files:
./setupBabel.js (to bootstrap Babel transpiler)
require('babel-polyfill');
require('babel-register');
./setupDependencies.js (to bootstrap Chai and Sinon using ES2015 syntax)
import chai from 'chai';
import sinon from 'sinon';
global.expect = chai.expect;
global.sinon = sinon;
./test/codeSpec.js (example Unit Test using Chai, Sinon Stubs, Spies, and ES2015 syntax such as arrow functions, let, and const)
describe('test suite', () => {
it('does not modify immutable constant value', () => {
const myStub = sinon.stub();
const mySpy = sinon.spy(myStub);
myStub.returns({});
let val = 1;
expect(val).to.not.equal(2);
sinon.assert.notCalled(mySpy);
});
});
Run the following in terminal to install relevant NPM packages:
npm install babel-polyfill babel-register chai sinon mocha
Run the test suite with the following terminal command and flags:
mocha --require ./setupBabel.js --require ./setupDependencies.js ./test/codeSpec.js
describe('异步钩子测试', function () {
const lover = {
bodyname: 'yueyue',
girlname: 'fangfang'
}
const test = 'lihang'
beforeEach('每一个测试之前的钩子', function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('通过')
})
})
})
it('方方爱张越', function () {
// 是否等于yueuyue
expect(lover.bodyname).to.equal('yueyue')
// 是否是一个字符串
lover.girlname.should.equal('fangfang')
})
})

Resources