Search code examples
angulartypescriptangular2-testing

sample code: making mocks in angular2


I am learning Angular2. In DI pages, there is sample code for mocking. https://angular.io/docs/ts/latest/guide/dependency-injection.html

What does it mean

let mockService = <HeroService> {getHeroes: () => expectedHeroes }

It looks like define mockService function from HeroService function.

What is <HeroService>? Is <HeroService> casting?

let expectedHeroes = [{name: 'A'}, {name: 'B'}]
let mockService = <HeroService> {getHeroes: () => expectedHeroes }

it('should have heroes when HeroListComponent created', () => {
  let hlc = new HeroListComponent(mockService);
  expect(hlc.heroes.length).toEqual(expectedHeroes.length);
});

Solution

  • To add to JB Nizet's answer and to give a little explanation on the reasoning behind the code.

    TypeScript uses Structural Type System1. What this means is that if it quacks like a duck, then it can be considered a duck (or more precisely, be compatible with a duck). Take for example

    class Duck {
      quack() { }
    }
    
    let duck = {
      quack: () => {}
    }
    

    Since duck has a quack method, you can pass it to anything that expects a Duck, like

    function doQuack(duck: Duck) {
      duck.quack();
    }
    
    doQuack(duck);
    

    TypeScript is smart enough to know that the duck object literal can be considered a Duck even if we never actually create an instance of a Duck using duck = new Duck(). This is because the structure of duck is enough to be compatible with the Duck type, because it matches the structure; the structure being only a single quack method.

    If we were to try to type duck as Duck, and we didn't have the quack method, then we would get a compile error.

    let duck: Duck = {   // compile error
      mooo: () => {}
    };
    
    let duck: Duck = {
      quack: () => {}    // OK
    }
    

    That being said, with your example, the HeroSerivce has two methods, one to get all the heroes, and one to get a hero by id.

    class HeroService {
      getHeroes(): Hero[] { .. }
      getHeroById(id: number): Hero { .. }
    }
    

    And a HeroComponent with a constructor that accepts a HeroService

    class HeroComponent {
      constructor(heroService: HeroService) {}
    }
    

    Now if we try to pass the following

    let mockService = { getHeroes: () => expectedHeroes }
    

    to the HeroComponent constructor, we will get a compile error because the mockService doesn't match the structure of a HeroService. It only has the one getHeroes method, when the structure actually consists of two methods, getHeroes and getHero.

    So to force the compiler to just accept it, we "cast" it to <HeroService>.

    We could pass the following (without "casting") and it would work, because it matches the structure.

    let mockService = {
      getHeroes: () => expectedHeroes,
      getHero: (id: number) => null
    };
    

    1 - Read more from TypeScript documentation chapter Type Compatibility