Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 CLI Jasmine unit test fails when using beforeAll instead of beforeEach

I created a new project with NG-CLI (beta.15) and modified the app.component.spec to change the beforeEach to to a beforeAll and it caused the tests to fail with the following error:

Failed: Cannot create the component AppComponent as it was not imported into the testing module!

I don't understand what this error means and of course why I would get it in the first place.

Here's the modified spec:

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('App: Ng2CliTest2', () => {
  beforeAll(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
    });
  });

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

  it(`should have as title 'app works!'`, async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    let app = fixture.debugElement.componentInstance;
    expect(app.title).toEqual('app works!');
  }));

  it('should render title in a h1 tag', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    let compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('app works!');
  }));
});

I then modified the spec to this and the first two tests pass and the third fails with the following message :

Failed: Attempt to use a destroyed view: detectChanges

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';

let fixture;
let app;

describe('App: Ng2CliTest2', () => {
  beforeAll(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
    });
    fixture = TestBed.createComponent(AppComponent);
    app = fixture.debugElement.componentInstance;        
  });

  it('should create the app', async(() => {
    expect(app).toBeTruthy();
  }));

  it(`should have as title 'app works!'`, async(() => {
    expect(app.title).toEqual('app works!');
  }));

  it('should render title in a h1 tag', async(() => {
    fixture.detectChanges();
    let compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('app works!');
  }));
});

I don't understand why there are any failures.

like image 973
HisDivineShadow Avatar asked Sep 28 '16 13:09

HisDivineShadow


2 Answers

Without knowing it, Angular actually resets the testing module in it's own undercover beforeEach (see testing.ts)

var _global = <any>(typeof window === 'undefined' ? global : window);

// Reset the test providers and the fake async zone before each test.
if (_global.beforeEach) {
  _global.beforeEach(() => {
    TestBed.resetTestingModule();
    resetFakeAsyncZone();
  });
}

I haven't even tried this, but it you want to know how it works to maybe try and (safely) disable this feature, this is what I've figured out:

Somewhere in your configuration you have imported

@angular/core/bundles/core-testing.umd.js

This is many times in a karma-test-shim.js file. This file contains pretty much all the testing utilities we use in a Angular test. It's pretty much a compilation of everything exported from the testing module. This include the above testing file that adds a global beforeEach call.

And just if it's not obvious from the above information your beforeAll is only good for the first test, then Angular resets the test bed. So the next test you are trying to create a component from an empty test bed configuration.

like image 175
Paul Samsotha Avatar answered Sep 27 '22 19:09

Paul Samsotha


For whoever is looking for a way to actually prevent angular from resetting the testbed: see this article

Relevant part:

import { TestBed, TestModuleMetadata } from '@angular/core/testing';
const resetTestingModule = TestBed.resetTestingModule;
const preventAngularFromResetting = () => TestBed.resetTestingModule = () => TestBed;
const allowAngularToReset = () => {
  resetTestingModule();
  TestBed.resetTestingModule = resetTestingModule;
};
export const setUpTestBed = (moduleDef: TestModuleMetadata, ...funcs: (() => void)[]) => {
  beforeAll(done => (async () => {
    resetTestingModule();
    preventAngularFromResetting();

    TestBed.configureTestingModule(moduleDef);
    funcs.forEach(func => func());

    TestBed.resetTestingModule = () => TestBed;
    return await TestBed.compileComponents();
  })().then(done).catch(done.fail));

  afterAll(() => allowAngularToReset());
};
like image 42
Maurits Moeys Avatar answered Sep 27 '22 17:09

Maurits Moeys