How to reuse all imports in Angular test files
Asked Answered
B

3

10

Lets say I have a simple module AppModule which has many imports, declarations and providers. Now I want to write a test for a component ListComponent which is located in this module's declaration list. ListComponent itself uses many, (but not every) import of the AppModule. I do it like this:

import { TestBed } from '@angular/core/testing';
// +same copy-pasted list of imports from `AppModule`

beforeEach(done => {
    TestBed.configureTestingModule({
        imports: [
            // +same copy-pasted list of imports from `AppModule`
        ],
        declarations: [
            // +same copy-pasted list of declarations from `AppModule`
        ],
        providers: [
            {
                provide: Http,
                useClass: HttpMock,
            },
            {
                provide: Router,
                useClass: RouterMock,
            }
            // +same copy-pasted list of providers from `AppModule`
        ]
    });

It works, but surely it is an incorrect approach. I do not want to copy-paste so much. Maybe I can reuse the AppModule in some convenient approach? Pseudocode would be like:

let appModule = new AppModule();

beforeEach(done => {
    TestBed.configureTestingModule({
        imports: appModule.imports,
        declarations: appModule.declarations,
        providers: [...appModule.providers,
            {
                provide: Http,
                useClass: HttpMock,
            },
            {
                provide: Router,
                useClass: RouterMock,
            }
        ]
    });

But I just do not know/cannot find the syntax for such approach :(

Borisborja answered 14/2, 2018 at 14:13 Comment(0)
S
15

You can create reusable const that contains the commom imports, providers from the modules you want.

for example in a app.providers.ts file you can have your providers like this:

import service1 from '.path/service/service1';
import service2 from '.path/service/service2';

export const providers = [service1, service2 ];

and for your imports in a app.imports.ts

import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { Module1} from ''.path/service/module1';

export const imports= [
    BrowserModule,
    AppRoutingModule,
    Module1
],

and on your app.module.ts and any other module you wanna use the same imports and providers you can do:

import { providers } from './app.providers';
import { imports } from './app.imports';
@NgModule({
    declarations: [AppComponent],
    imports: imports,
    providers: providers,
    bootstrap: [AppComponent]
})

You can also use the spread operator to add your unique imports to these shared imports on a specific module.

Scholasticism answered 14/2, 2018 at 15:33 Comment(2)
Thank you, this is exactly what I needed.Allahabad
I wonder, why such an approach is not default in angular? Why imports, providers, declarations need to be written out for each spec?Cervin
C
2

You can avoid providing long list of nested services and dependencies by created a global TestBed.

While creating constant arrays for providers and imports is one way to go about this, I wanted to take this to the next step and configure the TestBed on a global level to avoid importing repetitive modules.

In order to configure a global TestBed I created a common testing module which has the utility method to configure the testing bed. This method can then be reused across all the spec files.

public static setUpTestBed = (TestingComponent: any) => {
    beforeEach(() => {
      TestBed.configureTestingModule({
        imports: [
          ReactiveFormsModule,
          ...
        ],
        providers: [
          ...
        ],
        declarations: [TestingComponent],
        schemas: [CUSTOM_ELEMENTS_SCHEMA]
      });
    });
  }

CommonTestingModule: Contains utility method for creating the testing bed.
LoginComponent: login.component.spec.ts -> references to the utility method

CommonTestingModule.setUpTestBed(LoginComponent);

The full component is given below for reference:

CommonTestingModule:

import { ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { DatePipe } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { UtilityService } from '../services/utility.service';
import { TestBed } from '@angular/core/testing';

@NgModule({
  declarations: []
})
export class CommonTestingModule {

  public static setUpTestBed = (TestingComponent: any) => {
    beforeEach(() => {
      TestBed.configureTestingModule({
        imports: [
          ReactiveFormsModule,
          FormsModule,
          HttpClientTestingModule,
          RouterTestingModule,
          ... //other imports
        ],
        providers: [
          DatePipe,
          UtilityService,
          ... //other imports
        ],
        declarations: [TestingComponent],
        schemas: [CUSTOM_ELEMENTS_SCHEMA]
      });
    });
  }
}

And then in all your component spec files you can now reference to the utility method CommonTestingModule.setUpTestBed() which accepts the calling component name as the input parameter.

login.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
import { CommonTestingModule } from 'src/app/testing/common-testing.module';

describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;

  CommonTestingModule.setUpTestBed(LoginComponent);

  beforeEach(() => {
    // create component and test fixture
    fixture = TestBed.createComponent(LoginComponent);
    // get test component from the fixture
    component = fixture.componentInstance;
    component.ngOnInit();
  });

  it('Component instantiated successfully', () => {
    expect(component).toBeTruthy();
  });

});

That's it. You can now reuse the utility method in all your spec files. You can also create a beforeAll utility method if that suits you better.

Catto answered 14/11, 2020 at 15:57 Comment(0)
W
0

angular 15:

app-testing.module

@NgModule({
    imports: [
        ModalModule.forRoot(),
        NoopAnimationsModule,
        HttpClientTestingModule,
        TranslateTestingModule,
        RouterTestingModule
    ],
    providers: [
        { provide: ModalService, useClass: MockedModalService },
    ],
})
export class AppTestingModule {
}

some-component.spec.ts

beforeEach(async () => {
    await TestBed.configureTestingModule({
        imports: [
            AppTestingModule,
            SomeComponentModule,
        ],
    }).compileComponents();
});
Workmanship answered 1/12, 2023 at 7:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.