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?
Related
I am trying to run Jest unit tests on function logic that is imported from a Svelte component. The program runs fine except when I try to import into Jest: I can console log the env variable in the line below ok.
The problem is that this line gives me an error when i try to run the unit test, I guess because its trying to import the file into the jest test. It runs fine when the program is actually running, but when the Jest test tries to import it, the context changes or ... something. Anyway, here's the line, from my file src/routes/signup/index.svelte:
// in the script tag of a svelte component
<script context="module">
const googleRecaptchaSiteKey =
typeof import.meta.env.VITE_GOOGLE_RECAPTCHA_KEY === "string"
? import.meta.env.VITE_GOOGLE_RECAPTCHA_KEY
: ""
export function foo() {
// ...
}
</script>
This code must run in my Jest test because it gives the error when I run npm test, so here's how I am executing that code:
import { foo } from "../src/routes/signup/index.svelte"
// It must execute the whole component when I import from it?
describe("signup page logic", () => {
test("ensure that the signup form button enablement conditions work properly", () => {
const failureOne = foo()
}
}
The error message itself:
/home/rlm/Code/projName/src/routes/signup/index.svelte:446
const googleRecaptchaSiteKey = typeof import.meta.env.VITE_GOOGLE_RECAPTCHA_KEY === "string"
^^^^
SyntaxError: Cannot use 'import.meta' outside a module
> 1 | import { updateEnabledSubmitSignup } from "../src/routes/signup/index.svelte"
Now since writing the above text, I have been adventuring for approx 27 minutes to discover a solution. What I have done is try to follow guides.
Per the instruction of Environment variables with SvelteKit I did:
in src/lib/variables.ts:
export const variables = {
foo: import.meta.env.VITE_FOO,
secondRecaptchaKey: import.meta.env.VITE_SECOND_RECAPTCHA_KEY,
}
And then I import it into the Svelte file: import { variables } from "../../lib/variables"
I run npm run dev and it console logs the value fine.
But then when I run npm test I get:
src/lib/variables.ts:3:23 - error TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es2022', 'esnext', 'system', 'node12', or 'nodenext'.
3 secondRecaptchaKey: import.meta.env.VITE_SECOND_RECAPTCHA_KEY,
TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'esnext', or 'system'
Test suite failed to run import.meta.env.VITE_* does also but I tried to follow it and it fails even after installing vite-plugin-environment and babel-plugin-transform-import-meta and adding them to the babel plugins:
export const variables = { // logs with all values undefined
foo: process.env.VITE_FOO,
secondRecaptchaKey: process.env.VITE_SECOND_RECAPTCHA_KEY,
}
TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'esnext', or 'system' also has advice that fails for me, or i have done it wrong. In my current state npm test logs the env variables as undefined.
edit: For anyone in the future who has this problem, I was able to go around the problem by doing this totally-good-enough workaround:
jest.mock("../src/lib/envVariables", () => ({
envVariables: { foo: "bar", secondRecaptchaKey: "someMockValue" },
}))
I credit nstanard in this post for saving us from the hassle
I have the following setup:
// uuid-wrapper.ts
import { v4 as uuidV4 } from 'uuid';
const uuid: () => string = uuidV4;
export { uuid };
// uuid-wrapper.spec.ts
import { uuid } from './uuid-wrapper';
describe('uuid-wrapper', () => {
console.log(uuid());
});
This works fine runtime, but it breaks in test. I'm trying to migrate to Jest 29 and I'm getting the following error:
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export { default as v1 } from './v1.js';
^^^^^^
SyntaxError: Unexpected token 'export'
From uuid's repo I found a workaround which, after applied and my jest.config.js looks like this:
module.exports = {
moduleNameMapper: {
uuid: require.resolve("uuid"),
},
};
This gives me a different error but I still have no idea what it means:
> jest uuid-wrapper --no-coverage
FAIL src/uuid-wrapper/uuid-wrapper.spec.ts
● Test suite failed to run
TypeError: (0 , uuid_wrapper_1.uuid) is not a function
In fact, any function I export from this file (uuid-wrapper.ts) is not resolved. I have other tests that follow a similar pattern of reexporting packages but only this one breaks. I'm using uuid 9.0.0 and jest 29.1.2.
Edit: After a bit more testing, it turns out that anything I import into the test is "not a function".
uuid ships as an ESModule and Jest should not need to transform it. Add it to your transformIgnorePatterns in your Jest config:
module.exports = {
transformIgnorePatterns: ['node_modules/(?!(uuid))'],
}
Edit: After a bit more testing, it turns out that anything I import into the test is "not a function".
I had very similar symptoms once: builds and works as excepted, yet ...is not function in jest. The culprit was a circular dependency I accidentally introduced with my story. Maybe check for that.
As I suspected, the issue was in the naming. Renaming the files and directory to just wrapper solved the issue.
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…)
I've had trouble spying on exported function in a NodeJS (v9.6.1) app using Jasmine.
The app is written in typescript, transpiled with tsc in a dist folder to be executed as javascript.
App
I have a Foo utils file (foo.utils.ts) which exports functions:
import {readFile} from "fs";
export function getData(filePath: string){
const data = readFile(filePath)
// various checks happens here.
return data;
}
And a Bar class in a bar.ts file:
import {getData} from "../utils/foo.utils
export class Bar {
public do(){
// ... does stuff
const data = getData(filePath);
// etc.
}
}
Test
Now I'm trying to spyOn the exported getData method to check if it've been called and to return a mocked value. I don't want to read file in my unit test (and even to use the real getData method at all)
The bar.spec.ts file for testing:
describe("Bar", ()=>{
let bar: Bar;
beforeEach(function() {
bar = new Bar();
})
it(" should do stuff", ()=>{
const spy = spyOn(???, "getData").and.returnValue([]);
bar.do();
expect(spy).toHaveBeenCalled();
})
});
Problems
As it is a NodeJS app, I've been trying to use global as the object to spy on, but I get the error:
Error: : getAttachementData() method does not exist
I've also tried to add
import * as fooutils from ".../src/utils/foo.utils
And spy on fooutils but I still goes through the exported function (and crashes when it tries to read the file...)
Now I'm kinda lost. From what I've found it's not really possible to mock an exported function (even though they should be added to the global object).
I thought about refactoring the utils file to create an utils class which exports static method and spying on them.
Questions
Am I doing something wrong ?
Is it possible to spy (and replace) exported function ?
Would using static method in class (rather than export function) work ?
Any other way to that ?
You can wrap getData in an object { getData } and write your unit test as follows.
import { getData } from "../utils/foo.utils";
...
it(" should do stuff", () => {
const spy = spyOn({ getData }, "getData").and.returnValue([]);
bar.do();
expect(spy).toHaveBeenCalled();
});
I wanted to run some Spectron e2e tests I wrote some weeks ago, but to my surprise, suddenly they all stopped working for one and the same reason.
According to the error message, I'm dealing with rejected Promises, but I can't figure out where the problem is coming from. Calling done at the end of my testcase raises the exact same error.
I'm running the following command to launch my test suit: mocha test/e2e
Mocha then executes this index.js before running my tests in ordner to support ES6+ features
'use strict'
//index.js
// Set BABEL_ENV to use proper env config
process.env.BABEL_ENV = 'test'
// Enable use of ES6+ on required files
require('babel-register')({
ignore: /node_modules/
})
// Attach Chai APIs to global scope
const { expect, should, assert } = require('chai')
global.expect = expect
global.should = should
global.assert = assert
// Require all JS files in `./specs` for Mocha to consume
require('require-dir')('./specs')
After that its trying to run this small Login.spec.js which returns the error mentioned above
import utils from '../utils'
import {Application} from "spectron";
import electron from "electron";
describe('🔑 Login', function () {
this.timeout(11000);
it('login form exists', async function (done) {
this.app = new Application({
path: electron,
env: {"SPECTRON_RUNNING":true},
args: ['dist/electron/main.js'],
startTimeout: 10000,
waitTimeout: 10000
})
await this.app.start()
await this.app.client.windowByIndex(1);
done();
})
})