node.jsunit-testingjasmineelectronjasmine-node

How to unit test a non-module with jasmine-node? (related to turning off node integration in Electron)


I have a bit of a dilemma.

I turned on contextIsolation and turned off nodeIntegration for my BrowserWindow and loaded a local html page. (as is recommended by Electron).

This local html page requires a node module, which causes a crash, because Electron can't load a node module in the window since node is disabled:

main.ts:

import { app, BrowserWindow } from "electron";
import * as path from "path";

function createWindow() {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, "preload.js"),
    },
  });

  mainWindow.loadFile(path.join(__dirname, "../index.html"));
  mainWindow.webContents.openDevTools();
}

(async () => {
  await app.whenReady();
  createWindow();
})();

app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

index.html:

<body>
  <h1>Hello World!</h1>
  <script src="./renderer.js"></script>
</body>

renderer.js:

export interface A { }

Uncaught ReferenceError: exports is not defined at renderer.js:2

The specific line that crashes is this:

Object.defineProperty(exports, "__esModule", { value: true }); which exists because of the export statement.

I can remove the export in this case (and just redefine the interface in the other module that needs it), but then I can't unit test my script.

I'm currently using jasmine-node for unit testing and I've been requiring the module that I want to test at the top:

import * as Renderer from "./renderer"

This errors out in VSCode with something about how Renderer isn't a module.

It seems that requiring works (I guess Node can require non-modules?), but this causes the IIFE to run before my spies are set up.

If Renderer were a module, I would do:

<script>
   require("renderer.js").main();
</script>

And then my unit tests would set up the spies on the module before calling the main method. But since I can't require in my index.html since I've disabled Node, I have to load the script inline, and so I need some kind of IIFE to replace the main method:

(async () => {
   await doSomething();
})();

How can I unit test this non-module then with Jasmine?


Solution

  • My solution ended up being hacky.

    First, I had to make my non-module a module, so that I could import it in my specs.

    I exported my interface and added a main method:

    export interface A { }
    export async function main(): Promise<void> {
        await doSomething();
    }
    

    To avoid the crash I was getting before when I was loading a script with Node disabled, I added this hack to my html page:

    <script>var exports = {};</script> // mutes the "exports not defined" error
    <script src="./renderer.js"></script>
    

    And then I made my IIFE call my main method if jasmine isn't running.

    (async () => {
        if (!window.jasmine) {
            main();
        }
    })();
    

    My unit tests meanwhile set up the spies and call the exported main method.