【问题标题】:Issue testing an Angular service with abstract class dependencies使用抽象类依赖项测试 Angular 服务的问题
【发布时间】:2020-07-31 08:51:37
【问题描述】:

我有一个服务,它需要依赖另一个服务,而另一个服务又需要依赖两个抽象类。

(ThemeConfigService -> (SettingsService -> SettingsLoader, NavigationLoader))

我已经让测试失败了,因为它找不到通过抽象类公开的方法(不是函数异常)。

我不知道我是怎么通过这个的,网上的各种搜索都证明不是很有帮助。

这是我试图充实测试“theme-config.service.ts”的主题配置服务

@Injectable({
  providedIn: 'root'
})
export class ThemeConfigService {

  constructor(
    private platform: Platform,
    private router: Router,
    private settings: SettingsService
  ) {
    // code removed for brevity
  }
}

这里是正在测试的服务“settings.service.ts

@Injectable()
export class SettingsService {

  constructor(public settingsLoader: SettingsLoader,
              public navigationLoader: NavigationLoader) { }

  public settings(): Observable<any> {
    return this.settingsLoader.retrieveSettings();
  }

  public navigation(): Observable<any> {
    return this.navigationLoader.retrieveNavigation();
  }
}

这是SettingsLoader 类,NavigationLoader 看起来完全一样。从设计的角度来看,它们必须是单独的类:

export abstract class SettingsLoader {
    abstract retrieveSettings(): Observable<any>;
}

我的单元测试看起来像这样:

describe('ThemeConfigService', () => {
  let service: ThemeConfigService;
  let router: Router;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule.withRoutes([])
      ],
      providers: [
        Platform,
        SettingsService,
        SettingsLoader,
        NavigationLoader
      ]
    });

    router = TestBed.inject(Router);
    service = TestBed.inject(ThemeConfigService);
  });

  it('should be created', async(inject([Platform, Router, SettingsService, SettingsLoader, NavigationLoader],
    (platform: Platform, router: Router, settings: SettingsService, settingsLoader: SettingsLoader, navigationLoader: NavigationLoader) => {

    expect(service).toBeTruthy();
  })));
});

Karma 返回的错误是:

TypeError: this.settingsLoader.retrieveSettings is not a function
which to me proves that it cannot resolve the abstract classes.

出于这个原因,我继续创建了这样的东西:

export class SettingsFakeLoader extends SettingsLoader {
    retrieveSettings(): Observable<any> {
        return of({});
    }
}

并尝试使用这些更改 SettingsLoaderNavigationLoader 类的注入,然后 Karma 回复:

NullInjectorError: R3InjectorError(DynamicTestModule)[ThemeConfigService -> SettingsService -> SettingsLoader -> SettingsLoader]: 
  NullInjectorError: No provider for SettingsLoader!

theme-config.service.spec.ts 文件修改后的beforeEach

beforeEach(() => {
    TestBed.configureTestingModule({
        imports: [
        RouterModule,
        RouterTestingModule.withRoutes([])
        ],
        providers: [
        Platform,
        SettingsService,
        SettingsFakeLoader,
        NavigationFakeLoader
        ]
    });

    router = TestBed.inject(Router);
    service = TestBed.inject(ThemeConfigService);
});

通常,我不会尝试测试我认为如此复杂的东西。也许我只是没有看到“解决方案”。

这个问题的答案会很有用,因为我稍后会在这个应用程序的开发过程中解决类似的场景。

【问题讨论】:

    标签: angular typescript unit-testing karma-jasmine


    【解决方案1】:

    Angular testing documentation 中有一个很好的例子说明您正在尝试做的事情:

    beforeEach(() => {
      // stub UserService for test purposes
      userServiceStub = {
        isLoggedIn: true,
        user: { name: 'Test User' },
      };
    
      TestBed.configureTestingModule({
         declarations: [ WelcomeComponent ],
         providers: [ { provide: UserService, useValue: userServiceStub } ],
         // ^^^^^^ Note use of `useValue` ^^^^^^
      });
    
      fixture = TestBed.createComponent(WelcomeComponent);
      comp    = fixture.componentInstance;
    
      // UserService from the root injector
      userService = TestBed.inject(UserService);
    
      //  get the "welcome" element by CSS selector (e.g., by class name)
      el = fixture.nativeElement.querySelector('.welcome');
    });
    

    您可以创建任何实现服务公共 API 的类的实例,然后在配置测试模块时为该实例提供 useValue。调用 TestBed.inject(UserService) 会将该值传递给依赖服务的构造函数。

    在您的情况下,您可以使用jasmine.createSpyObj&lt;SettingsLoader&gt;("SettingsLoader", ["retrieveSettings"]) 创建一个实现SettingsLoader API 的实例,然后将其作为useValue 传递给providers。现在,TestBed.inject(ThemeConfigService) 将调用真正的 SettingsService 构造函数,将您创建的 spy 对象传递给它,然后调用 ThemeConfigService 构造函数,将生成的 SettingsService 实例传递给它。

    切换到此模型而不是您答案中的模型可能是个好主意,因为如果将来您例如更新你的Platform 以依赖SettingsService,你会突然发现自己的部分代码正在使用DI(platformrouter 是用inject(...) 创建的)但其中一部分不是t(你是 new-ing SettingsServiceThemeConfigService 你自己)。最好预先正确配置 DI,并允许注入器为您创建所有实例。

    【讨论】:

      【解决方案2】:

      在这里我将开发一点Coderer answer - 我使用 useClass 而不是 useValue

      TestBed.configureTestingModule({
        declarations: [...],
        imports: [...],
        providers: [
          { provide: SettingsLoader, useClass: SettingsFakeLoader },
          ...
        ],
        schemas: [CUSTOM_ELEMENTS_SCHEMA],
      })
      

      所以模式是:添加到providers 部分以下服务

      { provide: AbstractService, useClass: FakeService }
      

      (FakeService 类是 AbstractService 类的实现)

      【讨论】:

        【解决方案3】:

        我走的是实例化路线,而不是依赖依赖注入。这可能不是一个可行的解决方案,并且仍然希望有更好方法的人回答原始问题。

        theme-config.service.spe.ts 文件的更新 describe

        describe('ThemeConfigService', () => {
          let service: ThemeConfigService;
          let sLoader: SettingsLoader;
          let nLoader: NavigationLoader;
          let sService: SettingsService;
        
          beforeEach(() => {
            TestBed.configureTestingModule({
              imports: [
                RouterTestingModule.withRoutes([])
              ],
              providers: [
                ThemeConfigService,
                SettingsService,
                SettingsLoader,
                NavigationLoader
              ]
            });
        
            let platform = TestBed.inject(Platform);
            let router = TestBed.inject(Router);
        
            sLoader = new SettingsFakeLoader();
            nLoader = new NavigationFakeLoader();
        
            sService = new SettingsService(sLoader, nLoader);
            service = new ThemeConfigService(platform, router, sService);
          });
        
          it('should be created', () => {
            expect(service).toBeTruthy();
          });
        });
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-02-01
          • 2013-06-11
          • 1970-01-01
          • 1970-01-01
          • 2021-02-02
          • 2019-04-12
          相关资源
          最近更新 更多