1

Back in June 2016, I wrote an article on how to test Angular 2 applications. I used angular2-seed as a starting point.

https://raibledesigns.com/rd/entry/testing_angular_2_0_rc1

I decided to rewrite this tutorial using Angular CLI (from its master branch), which uses Angular 2 RC5. I'm seeing a strange error from one of my tests.

Error: Token must be defined!
    at new BaseException (/Users/mraible/ng2-demo/src/test.ts:1940:23 <- webpack:///Users/mraible/ng2-demo/~/@angular/core/src/facade/exceptions.js:27:0)
    at new ReflectiveKey (/Users/mraible/ng2-demo/src/test.ts:27600:19 <- webpack:///Users/mraible/ng2-demo/~/@angular/core/src/di/reflective_key.js:36:0)
    at KeyRegistry.get (/Users/mraible/ng2-demo/src/test.ts:27641:22 <- webpack:///Users/mraible/ng2-demo/~/@angular/core/src/di/reflective_key.js:77:0)
    at Function.ReflectiveKey.get (/Users/mraible/ng2-demo/src/test.ts:27615:35 <- webpack:///Users/mraible/ng2-demo/~/@angular/core/src/di/reflective_key.js:51:0)
    at ReflectiveInjector_.get (/Users/mraible/ng2-demo/src/test.ts:58418:62 <- webpack:///Users/mraible/ng2-demo/~/@angular/core/src/di/reflective_injector.js:586:0)
    at NgModuleInjector.get (/Users/mraible/ng2-demo/src/test.ts:40942:52 <- webpack:///Users/mraible/ng2-demo/~/@angular/core/src/linker/ng_module_factory.js:98:0)
    at TestBed.get (/Users/mraible/ng2-demo/src/test.ts:11910:47 <- webpack:///Users/mraible/ng2-demo/~/@angular/core/testing/test_bed.js:269:0)
    at /Users/mraible/ng2-demo/src/test.ts:11916:61 <- webpack:///Users/mraible/ng2-demo/~/@angular/core/testing/test_bed.js:275:46
    at Array.map (native)
    at TestBed.execute (/Users/mraible/ng2-demo/src/test.ts:11916:29 <- webpack:///Users/mraible/ng2-demo/~/@angular/core/testing/test_bed.js:275:0)

Here's my test:

import { provide } from '@angular/core';
import { TestComponentBuilder } from '@angular/compiler/testing';

import { MockActivatedRoute } from '../shared/search/mocks/routes';
import { MockSearchService } from '../shared/search/mocks/search.service';

import { EditComponent } from './edit.component';
import { ActivatedRoute } from "@angular/router";
import { inject } from "@angular/core/testing/test_bed";

describe('Component: Edit', () => {
  var mockSearchService:MockSearchService;

  beforeEach(() => {
    mockSearchService = new MockSearchService();

    return [
      mockSearchService.getProviders(),
      provide(ActivatedRoute, { useValue: new MockActivatedRoute({ 'id': '1' }) })
    ];
  });

  it('should fetch a single record', inject([TestComponentBuilder], (tcb:TestComponentBuilder) => {
    return tcb.createAsync(EditComponent).then((fixture) => {
      let person = {name: 'Emmanuel Sanders', address: {city: 'Denver'}};
      mockSearchService.setResponse(person);

      fixture.detectChanges();
      // verify service was called
      expect(mockSearchService.getByIdSpy).toHaveBeenCalledWith(1);

      // verify data was set on component when initialized
      let editComponent = fixture.debugElement.componentInstance;
      expect(editComponent.editAddress.city).toBe('Denver');

      // verify HTML renders as expected
      var compiled = fixture.debugElement.nativeElement;
      expect(compiled.querySelector('h3')).toBe('Emmanuel Sanders');
    });
  }));
});

I've posted this project to GitHub so you can reproduce this issue if you like: https://github.com/mraible/ng2-demo.

Matt Raible
  • 8,187
  • 9
  • 61
  • 120
  • any luck finding a solution for this? I'm hitting the same issue while trying to unit test via `ng test` through the angular cli tooling. – Danny Bullis Oct 28 '16 at 06:37
  • This was caused by my import - I had to change it to `import { TestBed } from '@angular/core/testing';`. You can see the whole test here: https://github.com/mraible/ng2-demo/blob/master/src/app/edit/edit.component.spec.ts – Matt Raible Oct 28 '16 at 15:37

1 Answers1

1

Matt, I've copied in what I think are the relevant lines of your working code to help any one who may stumble across this question in the future.

You mentioned your fix was to import TestBed from @angular/core/testing, and I noticed you also followed the guidance of injecting mock services, in a manner that is reflected in the angular testing documentation and guidelines as well.

My fix was a little different; it was due to a misnaming of one of the services that I had provided in the TestBed.configureTestingModule() method.

For example, in your case, you instantiate a new MockSearchService and pass it into the providers array as mockSearchService, as such: {provide: SearchService, useValue: mockSearchService}.

In my case, I passed in the MockSearchService type (NOTE, the actual type, not an instance), as follows: {provide: SearchService, useValue: MockSearchService} (note the capitalization)...

...and then got an instance of the service as follows: mockSearchService = fixture.debugElement.injector.get(MockSearchService);.

However, in my case, I mis-spelled the value I passed in as ...injector.get(mockSearchService) which was an invalid token to pass into the injector getter. So, I think this invalid token error is thrown by Karma, or one of the other tools involved in the test process as a catch all case for trying to access or pass in a token that is undefined. Moral of the story, it likely had to do with a misnaming of a variable somewhere. Your case is a little different, but I'm almost positive it was related somehow :]. Cheers!

Your working code

import { MockSearchService } from '../shared/search/mocks/search.service';
// other imports ...

describe('Component: Edit', () => {
    let mockSearchService: MockSearchService;
    // other service declarations...

    beforeEach(() => {
        mockSearchService = new MockSearchService();
        // other service instances...

        TestBed.configureTestingModule({
            // declarations...

            providers: [
                {provide: SearchService, useValue: mockSearchService},

                // other providers...
            ],

            // imports...
        });
    });

    // Tests ...

});
Danny Bullis
  • 3,043
  • 2
  • 29
  • 35