Skip to content

Create XhrMock test double#9338

Closed
taymonbeal wants to merge 3 commits intoampproject:masterfrom
google:mock-xhr
Closed

Create XhrMock test double#9338
taymonbeal wants to merge 3 commits intoampproject:masterfrom
google:mock-xhr

Conversation

@taymonbeal
Copy link
Copy Markdown
Member

Fixes #8020.

/** @const {!Window} */
this.win = win;
/** @type {function(this:Xhr, string, !FetchInitDef): !Promise<!FetchResponse>} */
this.fetchImpl = win.fetch ? this.nativeFetch_ : fetchPolyfill;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be private?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xhr-mock.js mutates it. Should it be private given that?

*
* See https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch
*
* @record
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not in externs? Or at least defined as @interface?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I was trying to do here was create a structural interface that would be satisfied by all three kinds of response object: browser-native Response, FetchPolyfillResponse as defined in this file, and MockFetchResponse as defined in xhr-mock.js. I'm not sure it entirely worked, since I still had to put type assertions in a couple places. How would you advise doing this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just leave this as it was, and do proper unit testing of the mock implementation.

* Create a copy of the response and return it.
* @return {!FetchResponse}
*/
clone() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these should have /** @override */

* interface is implemented by native `Headers` and by the polyfill and mock
* response headers types.
*
* @record
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, @interface

* @param {string} name
* @return {string}
*/
get(name) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto: /** @override */

@dvoytenko
Copy link
Copy Markdown
Contributor

@taymonbeal The double itself looks good. I'd still like to see how and where it helps in testing.

@taymonbeal
Copy link
Copy Markdown
Member Author

taymonbeal commented May 15, 2017

I designed this while simultaneously using it to write new tests for PR #9040. Those tests haven't been pushed yet because they necessarily rely on this PR, but here's an excerpt:

  const expectRequest = (serviceName, cachebusterKid = null) =>
      ({method, url, headers}) => {
        expect(method).to.equal('GET');
        let expectedUrl = endpoints[serviceName];
        if (cachebusterKid) {
          expectedUrl += '?kid=' + cachebusterKid;
        }
        expect(url).to.equal(expectedUrl);
        expect(headers['Origin']).to.equal(origin);
      };
  const makeResponse = (serviceName, kids) => ({
    status: 200,
    headers: {'Content-Type': 'application/jwk-set+json'},
    body:
        JSON.stringify({'keys': kids.map(kid => publicJwks[serviceName][kid])}),
  });
  const entry = (serviceName, kids, cachebusterKid = null) => ({
    request: expectRequest(serviceName, cachebusterKid),
    response: Promise.resolve(makeResponse(serviceName, kids)),
  });

  it('should verify a signature', () => {
    xhrMock.use(origin, [entry('service-1', ['key-1'])]);
    verifier.loadKeyset('service-1');
    return expect(verifier.verify(
                      'service-1', 'key-1', signatures['service-1']['key-1'][0],
                      creatives[0]))
        .to.eventually.be.null;
  });

  it('should verify multiple signatures with only one network request', () => {
    xhrMock.use(origin, [entry('service-1', ['key-1'])]);
    verifier.loadKeyset('service-1');
    return verifier
        .verify(
            'service-1', 'key-1', signatures['service-1']['key-1'][0],
            creatives[0])
        .then(failure => {
          expect(failure).to.be.null;
          verifier.loadKeyset('service-1');
          return expect(verifier.verify(
                            'service-1', 'key-1',
                            signatures['service-1']['key-1'][1], creatives[1]))
              .to.eventually.be.null;
        });
  });

  it('should verify a signature from a newly added key', () => {
    xhrMock.use(origin, [
      entry('service-1', ['key-1']),
      entry('service-1', ['key-2'], 'key-2'),
    ]);
    verifier.loadKeyset('service-1');
    return expect(verifier.verify(
                      'service-1', 'key-2', signatures['service-1']['key-2'][0],
                      creatives[0]))
        .to.eventually.be.null;
  })

*
* See https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch
*
* @record
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just leave this as it was, and do proper unit testing of the mock implementation.

*/
constructor(baseUrl, entries) {
/** @private {!Location} */
this.baseUrl_ = typeof baseUrl == 'string' ?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should always be a string.

@dvoytenko
Copy link
Copy Markdown
Contributor

@taymonbeal I see. I'd like to pass it on to @jridgewell and @aghassemi - they are more knowledgeable in tests and mocks.

@dvoytenko dvoytenko requested a review from aghassemi May 16, 2017 00:44
@jridgewell
Copy link
Copy Markdown
Contributor

Looking into this, why don't we go with an npm module? fetch-mock is active, and has a much nicer API.

@taymonbeal
Copy link
Copy Markdown
Member Author

I recall having looked at that at one point. Will it work with our Xhr services that sometimes use XMLHttpRequest instead?

@jridgewell
Copy link
Copy Markdown
Contributor

Looks like we need to set it up with our fetch polyfill:

fetchMock.setImplementations(fetchPolyfill);

Then, as long as there's a window.fetch, XHR will use it.

@taymonbeal
Copy link
Copy Markdown
Member Author

This is now a blocking issue for #9040, because that's going to get broken up into multiple PRs (it got too unwieldy to ship all at once and some parts are needed sooner for Origin Trials) and I need to refactor the existing tests to make them not depend on whether the old vs. new implementation is used.

I tried integrating fetch-mock myself but rapidly got lost due to not understanding how to make it work with the existing test doubles like in describes.js. @erwinmombay and I talked about this yesterday. Erwin, what would you say is the ETA on integrating fetch-mock? If it's not soon or not a priority, is there anything I can do to help? I have time to spend on this, but not expertise in AMP test doubles.

@dvoytenko
Copy link
Copy Markdown
Contributor

Close or still wip?

@jridgewell
Copy link
Copy Markdown
Contributor

Raph took over this in #10776, though he's gone now.

@taymonbeal
Copy link
Copy Markdown
Member Author

Obsoleted by #11001.

@taymonbeal taymonbeal closed this Aug 29, 2017
@taymonbeal taymonbeal deleted the mock-xhr branch August 29, 2017 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants