Angular 2 - 如何覆盖另一个模块中的依赖关系以进行测试

时间:2017-09-18 20:28:19

标签: angular unit-testing testing angular2-services

我正在尝试在我的模块中测试服务,该服务依赖于另一个模块中的服务。其他模块的服务,组件......可通过npm软件包访问。像大多数人一样,我的公司对于将源代码放在网上有点挑剔,所以希望我发布的内容足以获得帮助。

我想要模拟的服务获取用户信息并存在于另一个模块中。我收到一条错误消息,指出此服务返回的属性未定义。我尝试使用TestBed.overrideModule将其他模块中的提供程序更改为我创建的模拟版本。我在查找overrideModule的文档时遇到了麻烦,我已经尝试了几天,并且无法弄清楚如何制作我想要的工作,即使在找到一些overrideModules之后也是如此。任何帮助,将不胜感激。

基于我得到的错误,我想知道是否正在注入正确的依赖关系但是不知道如何检查。错误堆栈跟踪指向webpack:///node_modules/@another-repo/prefs/index.js。

代码(如果您有任何疑问,请告诉我,我试图删除可识别我公司的事项):

以下是我的模块

// classa.service.ts
import { Injectable } from '@angular/core';
import { IDModel } from '@another-repo/core';
import { FormatService } from '@another-repo/prefs';
import { MyRepo } from './my-repo.service';

@Injectable()
export class ClassA {
    constructor(idModel: IDModel, repo: MyRepo, formats: FormatService) {

    }
    doSomething() { }
}
// in my module
// my-repo.service.ts
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';

@Injectable()
export class MyRepo {
    constructor(private readonly http: Http) {
    }

    get(id): Observable<any> {
        return this.http
            .get(api(`${id}`))
            .map(response => response.json());
    }
}

// my-repo-service.spec.ts
//...
describe('test', () => {
    let testService;
    beforeEach(async () => {
        TestBed.configureTestingModule({
            imports: [
                HttpModule,
                FooModule
            ],
            providers: [
                { provide: RepoToMock1, useClass: MockRepoToMock1 },
                MyRepo,
            ],
        });

        TestBed.overrideModule(FooModule,
            {
                remove: {
                    providers: [RepoToMock1]
                },
                add: {
                    providers: [{ provide: RepoToMock1, useClass: MockRepoToMock1 }]
                }
            });
    });

    beforeEach(() => {
        testService = TestBed.get(MyRepo);
    });
    //...
});  

这是在node_modules中的index.d.ts文件中,让我们调用模块FooModule

export class IDModel {
    id: Observable<number>;
    constructor(otherIdRepo: OtherIdRepo, otherType: OtherTypeModel);
}

export class FormatService {
    constructor(formatModel: FormatModel, timeService: TimeService, numberService, NumberService);
}

export class FormatModel {
    myFormat: Format;
    constructor(repo: RepoToMock1);
}

export class Format {
    branding: string;
}

export class RepoToMock1 {
    constructor(http: Http);
    getPrefs(): Observable<Format>;
}

export class TimeService {
    constructor(formatModel: FormatModel);
}

export class NumberService {
    getNumber();
} 

2 个答案:

答案 0 :(得分:0)

我使用useFactory。请参阅下面的代码。

作为附注,我发现在测试中,所有依赖项都应该被模拟以进行注入。这解耦了代码库并允许更新注射剂,而不会影响依赖注射剂的物品测试。

例如,如果您要将X服务注入Y组件的测试而不模拟它,并且您决定X服务需要一个新功能,它引入了使用lodash的需要。所有组件Y的测试都会失败,因为您引入了新的依赖性并且没有考虑到它。无论您是否使用新的依赖于Lodash的功能,组件Y的测试都将失败。

如果在组件Y测试中模拟了服务X,则可以对服务X进行任何更改,而不会影响组件Y测试。

//y.component.spec.ts
describe('YComponent', () => {

  let component: YComponent;
  let fixture: ComponentFixture<YComponent>;
  let xService: XService;

  beforeEach(async(() => {
    let mockXService = {
      getTextColor: () => {}
    };

    TestBed.configureTestingModule({
      declarations: [
        YComponent,
      ],
      imports: [
        FormsModule
      ],
      providers: [
        {provide: XService, useFactory: () => { return mockXService; }}
      ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    }).compileComponents();

    fixture = TestBed.createComponent(YComponent);
    component = fixture.componentInstance;
    roomingService = TestBed.get(XService);

    fixture.detectChanges();
  }));

  it('should create', async(() => {
    // Assert
    expect(component).toBeTruthy();
  }));

});

答案 1 :(得分:0)

正如@ mgm87所写,您应该模拟所有服务和组件以进行简单的单元测试。 (作为旁注,对于组件模拟,我目前正在使用ng-mocks库,因为这使模拟变得非常容易->我与该库没有任何联系)

在测试中模拟服务时,大多数时候您仍然希望能够获得有关它们的一些信息,例如是否调用它,或者返回特定值以测试代码中的不同条件。这可以通过茉莉花和间谍来简单实现。

一个组件的单元测试设置示例如下:

Subject

然后您可以简单地致电

describe(ToTestComponent.name, () => {
  let component: ToTestComponent;
  let fixture: ComponentFixture<ToTestComponent>;
  let mockService: any;

  beforeEach(async(() => {
    mockService = {
      coolMethod: jasmine.createSpy('coolMethod')
    };
    TestBed.configureTestingModule({
      declarations: [
        ToTestComponent,
        MockComponent(SomeChildComponent) // example of ng-mocks library
      ],
      providers: [
        {provide: RealService, useValue: mockService}
      ]
    }).compileComponents();
  }));
...

});

如您所见,在这种情况下,您甚至不必导入其他模块(FooModule)。我将任何类型用作模拟服务的类型,否则您将必须满足类型要求。