Search code examples
angularjsjasmineecmascript-6angular-mock

Does Angular $httpBackend whenPUT() behave differently than whenGET()?


I am writing the beginning of an Angular service that will consume an API, and I started with stubbing out all the request actions. Nothing actually happens with this service yet; it's basically a wrapper for $http. I'm running into an interesting problem with the tests for this: I have mocked out a PUT request that doesn't seem to work as I expect it to, and I'm wondering where my understanding has failed.

Essentially, when I check for observation.get(1), the request is mocked correctly and the test passes. But if I do observation.update({'some_key': 'some_value'}), I get this error:

Chrome 51.0.2704 (Mac OS X 10.11.5) Observation responds to #save FAILED
    Error: No response defined !

I think I have defined a response in the beforeEach() function body, yet the spec still fails. It seems strange that every other test for this passes using the exact same syntax. What am I doing wrong?

Class (yay for ES6)

class Observation {
  constructor($http, AppSettings) {
    'ngInject';
    Object.assign(this, {$http, AppSettings});
    this.headers = {
      'Authorization': 'Token token=myfancytoken'
    };
    this.endpoint = `${this.AppSettings.API_URL}/api/${this.AppSettings.API_VERSION}/observations`;
  }

  get(id) {
    this.id = id;
    let params = {
      url: `${this.endpoint}/${this.id}.json`,
      method: 'GET'
    };
    return this.request(params);
  }

  save(data) {
    if (typeof this.id === 'undefined') {
      throw {message: 'Cannot save object without an id'};
    }
    let params = {
      url: `${this.endpoint}/${this.id}.json`,
      method: 'PUT',
      data: data
    };
    return this.request(params);
  }

  request(params) {
    Object.assign(params, {headers: this.headers});
    return this.$http(params)
      .then((response) => { return response.data; })
      .catch((response) => { return response.status; });
  }

  /*
  * Other actions omitted for brevity
  */
}

export default Observation;

Spec

import Observation from './observation';

describe('Observation', () => {
  let $httpBackend, AppSettings, observation, $http, obj, successResponse;

  beforeEach(() => {
    AppSettings = {
      "API_URL": 'http://localhost:3000',
      'API_VERSION': 'v1'
    };

    inject(($injector) => {
      $httpBackend = $injector.get('$httpBackend');
      $http = $injector.get('$http');
    });

    observation = new Observation($http, AppSettings);

    successResponse = (response) => {
      obj = response.data;
    };

    /* Mock the backend */

    // GET #show
    $httpBackend
      .when('GET', `${observation.endpoint}/1.json`)
      .respond(200, {data: {}});

    // PUT #save
    // FIXME: Not working for some reason and needs to be defined in the test itself?
    $httpBackend
      .when('PUT', `${observation.endpoint}/${observation.id}.json`)
      .respond(200, {data: {'id': 1}});
  });

  afterEach(function() {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });


  it('responds to #get', () => {
    observation
      .get(1)
      .then(successResponse);

    $httpBackend.expect('GET', `${observation.endpoint}/1.json`)
    $httpBackend.flush();

    expect(obj).toEqual({});
  });

  /* Other test cases omitted for brevity */

  it('responds to #save', () => {
    observation.id = 1;
    observation
      .save({'first_name': 'Lisa'})
      .then(successResponse);

    // FIXME: Why doesn't this work?
    $httpBackend
      .expect('PUT', `${observation.endpoint}/${observation.id}.json`);
      // .respond(200, {data: {'id': 1}});
    $httpBackend.flush();

    expect(obj.id).toBe(1);
  });

  it('throws an error if #save is called without an id on the object', () => {
    expect(() => {observation.save()}).toThrowError(Error, 'Cannot save object without an id');
  });

});

Solution

  • $httpBackend
      .when('PUT', `${observation.endpoint}/${observation.id}.json`)
    

    At this point observation has no id.