【问题标题】:Angular Testing: TestBed doesn't require providing ApplicationRefAngular 测试:TestBed 不需要提供 ApplicationRef
【发布时间】:2018-07-04 17:31:09
【问题描述】:

我正在按照https://angular.io/guide/testing 的官方“测试”指南为应用程序编写测试。

当我正在测试一个具有依赖项的服务时,我需要为这些依赖项提供模拟,但有趣的是,这对于 ApplicationRef 来说似乎并非如此,我真的很想知道为什么。

服务是这样的:

export class MyService {

    constructor(
        private dependentService: DependentService,
        private applicationRef:ApplicationRef
    ){}
    ...

以及相应的测试规范:

describe('MyService', () => {
    let dependentServiceSpy: jasmine.SpyObj<HttpClient>;

    beforeEach(() => {
        const dependentServiceSpy = jasmine.createSpyObj('DependentService', ['test']);

        TestBed.configureTestingModule({
            // Provide both the service-to-test and its (spy) dependency
            // why is 'ApplicationRef' not needed here?? 
            providers: [
                MyService,
                { provide: DependentService, useValue: dependentService_spy }
            ]
        });
    });
    ...   

});

由于 'DependentService' 和 'ApplicationRef' 都被注入到 MyService 构造函数中,我希望在 TestBed 的提供程序数组中都需要它们。但是,虽然省略 'DependentService' 会在测试中产生错误,但缺少的 'ApplicationRef' 不会

对此有什么合理的解释吗?

【问题讨论】:

    标签: angular testing


    【解决方案1】:

    每个使用 TestBed 的 Angular 测试配置都从以下初始化开始:

    getTestBed().initTestEnvironment(
      BrowserDynamicTestingModule,
      platformBrowserDynamicTesting()
    );
    

    Angular 测试环境将使用 BrowserDynamicTestingModule 来构造注入器,因为它适用于普通 Angular 模块(另见 https://blog.angularindepth.com/angular-dependency-injection-and-tree-shakeable-tokens-4588a8f70d5d)。

    Angular 合并 BrowserDynamicTestingModule 包含的提供者。该模块声明如下:

    @NgModule({
      exports: [BrowserTestingModule],
      providers: [
        {provide: TestComponentRenderer, useClass: DOMTestComponentRenderer},
      ]
    })
    export class BrowserDynamicTestingModule {
    }
    

    BrowserTestingModule 的样子:

                    /\
                    ||
    
    @NgModule({
      exports: [BrowserModule],
      providers: [
        {provide: APP_ID, useValue: 'a'},
        ELEMENT_PROBE_PROVIDERS,
        {provide: NgZone, useFactory: createNgZone},
      ]
    })
    export class BrowserTestingModule {
    }
    

    BrowserModule 是:

                    /\
                    ||             
    
    @NgModule({providers: BROWSER_MODULE_PROVIDERS, exports: [CommonModule, ApplicationModule]})
    export class BrowserModule {
      ...
    }
    

    最后ApplicationModule 声明ApplicationRef

                     /\
                     ||
    
    export const APPLICATION_MODULE_PROVIDERS: StaticProvider[] = [
      {
        provide: ApplicationRef,
        useClass: ApplicationRef,
        deps:
            [NgZone, Console, Injector, ErrorHandler, ComponentFactoryResolver, ApplicationInitStatus]
      },
      ...
    ];
    
    
    @NgModule({providers: APPLICATION_MODULE_PROVIDERS})
    export class ApplicationModule {
      // Inject ApplicationRef to make it eager...
      constructor(appRef: ApplicationRef) {}
    }
    

    如您所见,ApplicationRef provider 也被急切地实例化了。

    这样就没有什么魔法了,Angular 只是使用相同的算法,就好像它会从用户定义的 NgModules 中解析提供者一样

    【讨论】:

    • 虽然我还没有完全理解这里的 Angulars 机制,但这个答案肯定以非常深刻的方式回答了我的问题,所以接受它
    【解决方案2】:

    我不得不猜测,但我认为:

    每个组件都隐含着变化检测。如果不是,则不会进行自动更改检测。 但要直接在代码中使用此功能,您必须在构造函数中显式“捕获”该服务。

    现在,我们的 TestBed 将创建您在其中明确定义的那些服务。但它也会创建所有内部需要的服务(如 ChangeDetection,参见经典的“fixture.detectChanges()”)。 因此,您不必显式创建它。

    您提到的“DependentService”不是内部 Angular 魔法的一部分,因此您必须在 TestBed 中明确命名它。

    热烈的问候

    【讨论】:

    • 感谢您的回答,这基本上似乎已经确定了,@yurzui 的回答更加详细
    【解决方案3】:

    这可能是因为,我们在constructor 中注入了ApplicationRef,而没有在任何地方提供它。这是一个普通的interface,但不是provider。我们只提供 providers 内部测试 providers 数组,而不是我们注入的所有内容。 ChangeDetectorRef 也是如此。我们从不在 providers 数组中提供。

    【讨论】:

    • 对我来说确实有意义,因为我的应用程序代码不提供 ApplicationRef anyhwere(并且文档将其列为“接口”,正如您所提到的)。 @yourzui 的回答似乎更加详尽。我不得不承认,我只是还没有掌握整个依赖注入的东西......
    猜你喜欢
    • 1970-01-01
    • 2017-08-17
    • 2017-05-19
    • 2018-08-10
    • 2018-05-21
    • 2021-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多