Safa Alai
Safa Alai

Reputation: 1277

Stencil e2e Testing: mock a function in puppeteer browser context

I have Stencil component which uses axios.get to get some data from a server (e.g. localhost:3000 during development). Now I have an e2e.ts test which tests this component in conjunction with several other components. I would like to mock the axios.get function to isolate my tests from the server. In a spec.ts test I would mock axios using jest with the following code:

import axios from 'axios';
import {myComponent} from './my-component';

const mock = jest.spyOn(axios, 'get');

test('get data', async () => {
  ...
  mock.mockResolvedValue('hello');
  ...
});

But this does not work in e2e tests. I have tried installing jest-puppeteer but I cannot find any examples of how you would mock a function with jest mocking API with jest-puppeteer.

Any example code would be greatly appreciated.

P.S. Note: if I use Puppeteer to intercept a request and respond to it I get a "Request is already handled" error. Here's example code:

const page = await newE2EPage();
await page.setRequestInterception(true);
page.on('request', req => {
  if(req.url() === 'http://localhost:3000/') {
    request.respond({
      contentType: 'text/plain',
      headers: {'Access-Control-Allow-Origin': '*'},
      body: 'hello'
    })
  }
  else {
    request.continue({});
  }
});
await page.setContent('<my-component></my-component>');

Upvotes: 1

Views: 3117

Answers (1)

Simon H&#228;nisch
Simon H&#228;nisch

Reputation: 4978

I'm not 100% sure how well this answer translates to axios, but this is possible with fetch, which I encourage you to use because it's widely supported by browsers now, and Stencil automatically poly-fills it if needed.

For our app's e2e tests I wrote the following script that you can add to your e2e page after initalizing it:

await page.addScriptTag({
  content: `
    window.originalFetch = window.fetch;

    window.requestsToIntercept = [];

    window.fetch = (...args) => (async(args) => {
      const result = await this.originalFetch(...args);

      for (const request of requestsToIntercept) {
        if (args[0].includes(request.url)) {
          result.json = async () => JSON.parse(request.response);
          result.text = async () => request.response;
        }
      }

      return result;
    })(args);`,
});

It overwrites the fetch implementation and uses the global requestsToIntercept array to stub responses. You can add a helper function to your code like

const interceptRequests = async (requests: { url: string; response: string }[]) =>
  page.addScriptTag({
    content: `window.requestsToIntercept.push(...${JSON.stringify(requests)});`
  });

and then use it like

interceptRequests([{ url: '/foo', response: { foo: 'bar' } }])

This will intercept all requests that include /foo and respond with the given response instead.


I'll leave it up to you to refactor this into helpers the way you want. Personally I decided to create a function that creates a newE2EPage for me and adds interceptRequests as a method to the page object.


BTW the reason that you can't enable request interception in Puppeteer is that Stencil already uses it internally, and therefore the request will already have been handled before your "on request" listener kicks in (as the error message states). There's a request to change this on Github: https://github.com/ionic-team/stencil/issues/2326.

Upvotes: 3

Related Questions