mainmatter/breethe-client

Setup Pretender for tests

jorgelainfiesta opened this issue · 22 comments

We need to be able to run Pretender in Glimmer tests but I cannot figure out how to import the package in Typescript. I ran out of ideas on how to address this issue, my problem is reported here: pretenderjs/pretender#229

This issue has become a big blocker because I cannot write the tests for all the other features without this setup. Any ideas on alternatives or any work around that could work?

@Turbo87: can you spend some time with @jorgelainfiesta on this this week and try and figure this out?

I am unsure why we would use Pretender at all. Pretender only works with XMLHttpRequest AFAIK, and I would assume that for such a futuristic project we would use native fetch() instead?!

That's indeed a good point. I somehow assumed Pretender supported fetch. There is https://github.com/sstur/fetch-pretender but we should probably check what other alternatives there are. If there's not we could of course force a Polyfill when testing which would allow us to use Pretender again…

Orbit uses fetch indeed. However, you have to manually bind it to the window's fetch (orbitjs/orbit#452). I think we could bind it to the polyfill for testing.

@jorgelainfiesta: did you check whether there is anything like Pretender that supports fetch though?

I hadn't but I'm researching now. From early results it seems like fetch-mock is quite popular. It basically allows you to provide a response per matcher, quite similar to Pretender.

Another alternative, popular in Jest, is to completely override the fetch function with a mock function. I assume this is more popular in unit tests because it'd be quite troublesome to test a feature that calls different endpoints with this approach.

fetch-mock LGTM on first glance. I'd like to avoid mocking fetch by overriding it as that'll be more effort to maintain and likely also more brittle.

Awesome! I'll retake the tests tasks now.

Update: fetch-mock internally uses a babel plugin called transform-runtime for polyfills. However, this kind of configuration clashes with glimmer's use of rollup. The error I get literally says It looks like your Babel configuration specifies a module transformer. Please disable it..

😞 Is that the only one or are there other mocking solutions that support fetch?

I'm evaluating jest-fetch-mock. It doesn't seem to have a jest specific dependency. However, it's also a mock of the fetch function with some helpers. No matchers are used here.

Update: I also had build problems with jest-fetch-mock. However, I found some configurations that are possible on the build process that might enable us to use this addon or maybe even Pretender.

I'm experimenting with this configurations. So far I only get a cryptic error when adding this options to the rollup configuration. It simply says parse is not a function.

I'm transcribing some hints I got from Toran Billups so they don't get lost in Ember's community slack.

I've had success wrapping fetch w/ a simple es2015 module that I can monkey patch for tests
so in my app code I do import fetch from 'my/fetch';
in that module^ I do something like this

import fetch from 'fetch';

const _private = {
  _fetch(url) {
    return fetch(url);
  }
}

export default function(url) {
  return _private._fetch(url).then((response) => {
    if (response.ok) {
      return response;
    }
    throw response;
  });
}

export { _private };

then in your tests you can write a simple helper to tap into that _fetch and return whatev you want

import { _private } from 'my/flash';

const patchFetch = function patchFetch(callback) {
  const origFunc = _private._fetch;
  _private._fetch = function() {
    try {
      callback(arguments[0]);
    } finally {
      _private._fetch = origFunc;
    }
    return origFunc.apply(this, arguments);
  };
};

the funky _private object you see above^ is required here because anything exported from an es2015 module is readonly meaning you can't alter it like you see inside the patchFetch function
that little helper^ would allow for simple fetch stubbing from component integration/ or unit tests

here is a quick look at how I'm using the patch method today (just simple url path verification mostly but could be used to return a promise w/ json if needed)

    patchFetch((url) => {
      assert.equal(url, '/foobar');
    });

Update: the most promising solution seemed to mock Orbit's fetch in the test. Each component that loads data has to be given a store instance by the entry component. So we could also mock the Store in the test.

While implementing this solution, we came across a huge limitation in Glimmer's testing story: you cannot bind context variables when rendering a component in a test (glimmerjs/glimmer.js#14). We'l have to skip tests for now.

As a reference for the future, below is a draft of how the test could've looked if it wasn't because of this problem:

import hbs from '@glimmer/inline-precompile';
import { setupRenderingTest } from '@glimmer/test-helpers';
import Orbit from '@orbit/data';
import setupStore from '../../../utils/data/setup-store';

const { module, test } = QUnit;

const locations = [
  {
    id: 1,
    type: 'location',
    attributes: {
      'city': 'København',
      'coordinates': '55.676098, 12.568337',
      'country': 'Denmark',
      'last-updated': '2017-03-06'
    }
  },
  {
    id: 2,
    type: 'location',
    attributes: {
      'city': 'Salzburg',
      'country': 'Austria',
      'last-updated': '2017-03-07',
      'coordinates': '47.811195, 13.033229'
    }
  }
];

module('Component: Home', function(hooks) {
  setupRenderingTest(hooks);

  test('it renders', async function(assert) {
    Orbit.fetch = () => {
      return new Promise((resolve, reject) => {
        resolve({
          data: locations
        });
      });
    };

    this.store = setupStore();

    await this.render(hbs`<Home store={{store}} />`);

    let location1 = this.containerElement.querySelector('.test__location-1');
    assert.ok(!!location1, 'Location 1 loaded');

    let location2 = this.containerElement.querySelector('.test__location-2');
    assert.ok(!!location2, 'Location 2 loaded');
  });
});

have you tested using this.set('store', setupStore()) instead?

this.set doesn't exist anymore in Glimmer.

yeah, but the testing context is not aware of Glimmer afaik

although I just noticed that you're importing from @glimmer/test-helpers, so maybe I'm wrong... no idea how that package works

I did try but I get an error saying that this.set is not a function. You can actually see that this feature is not yet implemented in glimmer testing: glimmerjs/glimmer.js#14

Since Glimmer doesn't even allow proper testing at this stage and has quite a few other issues I'm wondering if this is the right solution at this point. Given blog posts like https://madhatted.com/2017/6/16/building-a-progressive-web-app-with-ember it might be a lot easier to build the PWA with Ember.js instead 🤔

I think building this app with Ember would be way easier. But I think the intention of using Glimmer and Orbit etc, is to show that we can handle the edgy technologies?

Yes, the idea is to use the latest and (to-be) greatest - basically showing off lightest/fastest etc. possible. So for now Ember is not an option here.