Search code examples
javascriptajaxrxjsrxjs5nock

RxJS: How to test Observable.ajax() with nock?


I'm currently learning RxJS and I'm trying to figure out how to test Observable.ajax() using nock.

Here's my simplified example without any assertions... I just want to see if nock can intercept that API call or not.

import { afterEach, describe, it } from 'mocha';
import nock from 'nock';
import { ajax } from 'rxjs/observable/dom/ajax';

// had to use `crossDomain: true` for it to work in production code
const api = ajax({
  url: 'https://server.com/abc',
  crossDomain: true,
});

describe('ajax', () => {
  afterEach(() => {
    nock.cleanAll();
  });

  it('given valid call, should return value', (done) => {
    nock('https://server.com', {
      // needed to prevent "cross origin null forbidden" error
      reqheaders: {
        'Access-Control-Allow-Origin': '*',
      },
    }).get('/abc').reply(200, 'YAY!');

    api
      .map(e => e.json())
      .subscribe(
        value => console.log('SUCCESS', value),
        error => console.log('ERROR', error),
        () => done(),
      );
  });
});

When executing the above code, I'm not able to get the intended value ("YAY!").

Instead I got this error:-

Error: Error: Nock: No match for request {
  "method": "GET",
  "url": "https://server.com/abc",
  "headers": {
    "referer": "about:blank",
    "user-agent": "Mozilla/5.0 (darwin) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/11.2.0",
    "accept-language": "en",
    "accept": "*/*",
    "origin": "null",
    "host": "server.com",
    "accept-encoding": "gzip, deflate"
  }
}

The console.log(..) on subscribe(..) displays this:-

ERROR { [Error: ajax error]
  message: 'ajax error',
  xhr: 
   XMLHttpRequest {
     upload: XMLHttpRequestUpload { _ownerDocument: [Object] },
     _eventHandlers: { readystatechange: [Object] },
     [Symbol(flag)]: 
      { synchronous: false,
        withCredentials: false,
        mimeType: null,
        auth: null,
        method: 'GET',
        responseType: 'json',
        requestHeaders: {},
        referrer: 'about:blank',
        uri: 'https://server.com/abc',
        timeout: 0,
        body: undefined,
        formData: false,
        preflight: false,
        requestManager: [Object],
        pool: undefined,
        agentOptions: undefined,
        strictSSL: undefined,
        proxy: undefined,
        cookieJar: [Object],
        encoding: 'UTF-8',
        origin: 'null',
        userAgent: 'Mozilla/5.0 (darwin) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/11.2.0' },
     [Symbol(properties)]: 
      { beforeSend: false,
        send: false,
        timeoutStart: 0,
        timeoutId: 0,
        timeoutFn: null,
        client: [Object],
        responseHeaders: {},
        filteredResponseHeaders: [],
        responseBuffer: null,
        responseCache: null,
        responseTextCache: null,
        responseXMLCache: null,
        responseURL: '',
        readyState: 4,
        status: 0,
        statusText: '',
        error: [Object],
        uploadComplete: true,
        uploadListener: false,
        abortError: false,
        cookieJar: [Object],
        bufferStepSize: 1048576,
        totalReceivedChunkSize: 0,
        requestBuffer: null,
        requestCache: null,
        origin: 'null' } },
  request: 
   { async: true,
     createXHR: [Function: createXHR],
     crossDomain: true,
     withCredentials: false,
     headers: {},
     method: 'GET',
     responseType: 'json',
     timeout: 0,
     url: 'https://server.com/abc',
     body: undefined },
  status: 0 }

My question is how do I configure nock to properly intercept that API call?

Is there a better way to test that API call without using nock?

Thank you.


Solution

  • I figured it out. The key is to define url that matches API under test when initializing JSDOM.

    Here's the working version with assertions:-

    import { afterEach, describe, it } from 'mocha';
    import nock from 'nock';
    import { expect } from 'chai';
    import { ajax } from 'rxjs/observable/dom/ajax';
    import { JSDOM } from 'jsdom';
    
    // defining `url` is important!!
    const { window } = new JSDOM('', { url: 'https://server.com' });
    
    const apiUnderTest = ajax({
      url: 'https://server.com/abc',
      crossDomain: true,
      createXHR: () => new window.XMLHttpRequest(),
    });
    
    describe('ajax', () => {
      const expectedValue = 'YAY!';
    
      afterEach(() => {
        nock.cleanAll();
      });
    
      it('given successful call, should return value', (done) => {
        nock('https://server.com').get('/abc').reply(200, { value: expectedValue });
    
        apiUnderTest
          .map(e => e.response.value)
          .subscribe(
            (actualValue) => {
              expect(actualValue).to.deep.equal(expectedValue);
              done();
            },
            (error) => {
              expect(error).to.be.an('undefined');
              done();
            },
          );
      });
    
      it('given failed call, should not return value', (done) => {
        nock('https://server.com').get('/abc').reply(400, { value: expectedValue });
    
        apiUnderTest
          .map(e => e.response.value)
          .subscribe(
            (actualValue) => {
              expect.fail(actualValue, undefined, 'Should not have value');
              done();
            },
            (error) => {
              expect(error).to.not.be.an('undefined');
              done();
            },
          );
      });
    });