GoogleChrome / chrome-launcher

Launch Google Chrome with ease from node.
https://www.npmjs.com/package/chrome-launcher
Apache License 2.0
1.24k stars 189 forks source link

How do I stub "chrome-launcher" with sinon? #204

Open simonespa opened 4 years ago

simonespa commented 4 years ago

Hello, thanks for the hard work on this library, it is really great.

I've got a module that exports the following function (among many others)

import puppeteer from 'puppeteer-core';
import * as ChromeLauncher from 'chrome-launcher';
import fetch from 'node-fetch';

export const launchBrowser = async (options = {}) => 
  // Launches Chrome
  const chrome = await ChromeLauncher.launch(options);
  // Gets the port Chrome is running on
  options.port = chrome.port;
  // Connect puppeteer to the running Chrome instance
  const response = await fetch(`http://localhost:${options.port}/json/version`);
  const { webSocketDebuggerUrl } = await response.json();
  return puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl });
};

I'm using mocha in conjunction with chai and sinon to test it. I've got the following test where I'm calling import * as ChromeLauncher from 'chrome-launcher' before the module to test so that I can apply the stub correctly.

import sinon from 'sinon';
import * as ChromeLauncher from 'chrome-launcher';
import { launchBrowser } from '...';

describe('something', () => {
  const sandbox = sinon.createSandbox();

  afterEach(() => sandbox.restore());

  it('should do something', async () => {
      const chrome = {
        port: '3434'
      };
      sandbox.stub(chromeLauncher, 'launch').resolves(chrome);

      ... ... ...

      await launchBrowser(options);

      sandbox.assert.calledOnceWithExactly(ChromeLauncher.launch, options);
    });
});

As you can see, I'm using sandbox.stub(chromeLauncher, 'launch').resolves(chrome) to decorate the launch function and resolve with a fake chrome object.

Unfortunately it doesn't work, and it calls the real chrome-launcher.

My question is, how can I stub the launch function properly? Do you provide mocks ready to use?

Thanks, hope you've got all the details you need.

paulirish commented 4 years ago

oh boy this was annoying.

I guess we get to thank the typescript compiler for this one.

You seem to be doing everything right. It's just being real difficult about stubbing the actual modules's method.

I looked at ./node-modules/chrome-launcher/dist/index.js.. it's currently this absolute mess:

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p);
}
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(, exports);
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQUEsb0RBQWtDIn0=

I replaced it with its idiomatic equivalent:

module.exports = require("./chrome-launcher");

and then the stubbing started working.

So perhaps there's some compilerOptions we can tweak for more reasonable output.

paulirish commented 4 years ago

So perhaps there's some compilerOptions we can tweak for more reasonable output.

or not. played around with module and target but was unsuccessful.

I googled for typescript sinon stub and there's a bunch of stuff out there. Based on the dumb JS in the entrypoint you might have to do mockModule.

Another alternative is we publish chromelauncher in both commonjs and esm.

btw thx for coming by and opening the issue. :)

simonespa commented 4 years ago

I googled for typescript sinon stub and there's a bunch of stuff out there. Based on the dumb JS in the entrypoint you might have to do mockModule.

Hey @paulirish thanks for looking into it, i'll see if I can find a workaround in the meantime and I'll get back to you.

Another alternative is we publish chromelauncher in both commonjs and esm.

This would be great, looking forward to that.

Out of curiosity, are you guys using the TS compiler to strip the Typescript types out? If I may, we also have some small libraries that use TS, but we only use its compiler (and config) for type checking, the transpilation (and type stripping) is still done via Babel which is way better at handling JS quirks and the ever changing language specification.

btw thx for coming by and opening the issue. :)

No problem, I just couldn't get my head around it, so I figured I could ask you directly :)

westy92 commented 4 years ago

I was just looking into this and I tracked down https://github.com/microsoft/TypeScript/issues/38568.

In the meantime, I'm going to pin "chrome-launcher": "0.13.2" which allows stubs.

simonespa commented 3 years ago

Hi @westy92 @paulirish is this issue fixed then? Can we say it is now possible to mock the chrome-launcher module with sinon?

Thanks both 😄

westy92 commented 3 years ago

@simonespa I was able to use proxyquire to mock chrome-launcher: https://github.com/westy92/html-pdf-chrome/pull/297