Search code examples
javascripttestingaureliaaurelia-di

Aurelia inject mock dependency


I have this aurelia component for displaying a feed to the user which depends on a custom API service class called Api for fetching the feed. The Api class has a get() function which in turn uses HttpClient to fetch the data.

Trying to test the component I want to mock the service class, specifically the get function, to return suitable test data and have this mock injected into the component via aurelia's DI container. The DI part I am having trouble with.

Here is the relevant part of component's js file

import {bindable, inject} from 'aurelia-framework';
import {Api} from 'services/api';

@inject(Api)
export class Feed {
  events = null;

  constructor(api) {
    console.info('feed.js constructor, api:', api)
    this.api = api;
  }

And the relevant code from my test

  beforeEach(done => {
    ...
    let mockApi = new Api();
    spyOn(mockApi, 'get').and.returnValue(mockGetResponse);

    const customConfig = (aurelia) => {
      let conf = aurelia.use.standardConfiguration().instance("Api", mockApi);
      console.info('Registering Api:', conf.container.get("Api"));
      return conf;
    }

    const ct = new ComponentTester();
    ct.configure = customConfig;

    sut = ct.withResources('activityfeed/feed');
    sut.inView('<feed username.bind="username"></feed>')
        .boundTo({username: TEST_USER});

    sut.create(bootstrap).then(() => {
      done();
    });
  });

This code is actually working the way I intended as far as I can tell. On creation of the component my customConfig function is called and the mockApi instance is logged to the console.

However later in the bootstrapping process the component constructor still receives an instance of the actual Api service class instead of my mock instance which was registered to the container.

Spent the last couple of hours trying to dig up any documentation or examples for doing things like this without success so if anyone can assist I would greatly appreciate it.

Or if there is / are alternative ways to accomplish this that would work just as well.


Solution

  • When testing a standard component that consists of both a view and a view model, using the aurelia-testing package, I find that a cleaner approach might be to let Aurelia create both the view and view-model, and use mocked classes for all view model dependencies.

    export class MockApi {
      response = undefined;
    
      get() { return Promise.resolve(this.response) }
    }
    
    describe("the feed component", () => {
      let component;
      let api = new MockApi();
    
      beforeEach(() => {
        api.response = null;
    
        component = StageComponent
          .withResources("feed/feed")
          .inView("<feed></feed>");
    
        component.bootstrap(aurelia => {
          aurelia.use
            .standardConfiguration();
    
          aurelia.container.registerInstance(Api, api);
        });
      });
    
      it("should work", done => {
        api.response = "My response";
    
        component.create(bootstrap).then(() => {
          const element = document.querySelector("#selector");
          expect(element.innerHTML).toBe("My response, or something");
    
          done();
        });
      });
    });
    

    This approach lets you verify the rendered HTML using the normal view model class, mocking the dependencies to control the test data.