Angular2 RC5 how to properly configure test module

2020-07-03 04:20发布

问题:

I'm updating my unit tests for Angular2 RC5. The changelog notes the following breaking change:

addProviders [is deprecated], use TestBed.configureTestingModule instead

But this seems to take error only when attempting to include a service in a test. Where my unit test used to do the following:

beforeEach(() => addProviders([
    MyService,
    MockBackend,
    ... 
]));

it should now configure the test module:

TestBed.configureTestingModule({
    providers: [
        StoryService,
        MockBackend,
        ...
    ]
});

But that now throws an error

Service: MyService encountered a declaration exception FAILED

Error: Cannot configure the test module when the test module has already been instantiated. Make sure you are not using inject before TestBed.configureTestingModule.

I have checked that inject isn't been called before the configureTestingModule. This doesn't affect any other component/directive tests, they seem to pass just fine. How can I resolve this error for unit testing a service with RC5? I realize that I may have to wait until the the testing documentation are updated for RC5 but any insight into possible solutions would be much appreciated.

回答1:

From what I have read, you only need to init the test environment once. You can then reset the testing module and configure the testing module before each test:

beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [ GridComponent ]
        });
    });

    afterEach(() => {
        TestBed.resetTestingModule();
    });

The upfront configuration is:

var testing = require('@angular/core/testing');
var browser = require('@angular/platform-browser-dynamic/testing');

try {
    testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting());
} catch ($error) {
    console.log("test env failure" + $error);
}

After reading through the code it seems configureTestingModule will call resetTestingModule internally but I like to declare it in after each for readability purposes.



回答2:

I had a similar issue and fixed it by resetting the test environment:

import {MyService} from './my.service';
import {inject, TestBed} from '@angular/core/testing/test_bed';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } 
       from '@angular/platform-browser-dynamic/testing';

describe('MyService', () => {
  beforeEach(() => {
    // Must reset the test environment before initializing it.
    TestBed.resetTestEnvironment();

    TestBed
      .initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting())
      .configureTestingModule({
        providers: [
          MyService
        ]
    });
  });

  it('should do something', inject([MyService], (s: MyService) => {
    let r = s.getResult();
    expect(r.length).toBe(2);
  }));
});

I am quite the noob when it comes to Angular 2 so I welcome corrections from more knowledgeable folks. It seems that one should not have to reset and reinitialize the test environment in every test. Unfortunately, it was the only way I was able to get it working.



回答3:

Stick TestBed.configureTestingModule inside beforeEach like so:

beforeEach(() => {
    TestBed.configureTestingModule({
        providers: [
            StoryService,
            MockBackend,
            ...
]
    });
});


回答4:

The problem arise when the TestBed is initialized outside a before each when having two spec files or more.

For example:

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));
describe('AppComponent', () => {
  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });
 });

will instanciate a TestBed beforeEach tests, not only the spec file. Hence, if you have another .spec with a TestBed and a beforeEach it will be interpreted as 2 TestBed instanciated like this:

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));
describe('AppComponent', () => {
  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });
 });

The error

Failed: Cannot configure the test module when the test module has already been instantiated.

will be right, since you instanciate two TestBed (but in two spec files).

To solve this problem, you must always put the TestBed definition (so the beforeEach) in a describe like this:

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });
});