【问题标题】:Angular2 Unit Test Not Using Mock ServiceAngular2单元测试不使用模拟服务
【发布时间】:2017-07-08 11:53:17
【问题描述】:

我正在测试一个显示/隐藏登录/退出按钮的非常简单的组件。

为此,我正在嘲笑我的 AuthService 服务,因为它依赖于 AngularFire2。

我遇到的问题是没有提供我的模拟服务 (Mock AuthService) 来代替实际的 AuthService

在测试should show the Facebook login button 中,service.isAnonymous 预计是未定义的。在实际服务中,确实如此。但是在模拟服务中是true。这个测试应该失败。

另外,请注意我正在尝试调用方法service.test(false);;在模拟服务中,此方法存在并且是public。但我收到错误:

“AuthService”类型上不存在属性“test”。

这表明没有提供我的模拟服务。

您可以在我的测试规范中看到我如何尝试两种方式来提供模拟服务(其中一种已被注释掉):

import { DebugElement } from '@angular/core';
import {
  async,
  inject,
  ComponentFixture,
  TestBed
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';

import { FacebookLoginComponent } from './facebook-login.component';
import { AuthService } from '../shared/auth.service';
import { MockAuthService } from '../shared/testing/auth.service';

describe('FacebookLoginComponent', () => {
  let authService: AuthService;
  let component: FacebookLoginComponent;
  let fixture: ComponentFixture<FacebookLoginComponent>;
  let debugElement: DebugElement;
  let htmlElement: HTMLElement;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ FacebookLoginComponent ],
      // providers: [{ provide: AuthService, useValue: MockAuthService }]
    })
      .compileComponents();

    TestBed.overrideComponent(FacebookLoginComponent, {
      set: {
        providers: [{ provide: AuthService, useClass: MockAuthService }]
      }
    })
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(FacebookLoginComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should be created', () => {
    expect(component).toBeTruthy();
  });

  it('should show the Facebook login button', inject([ AuthService ], (service: AuthService) => {
    expect(service.isAnonymous).toBeUndefined();

    debugElement = fixture.debugElement.query(By.css('button'));
    htmlElement = debugElement.nativeElement;

    service.test(false);

    expect(htmlElement.textContent).toBe('Facebook Login');
  }));

  it('should show the Logout button', () => {
    debugElement = fixture.debugElement.query(By.css('button'));
    htmlElement = debugElement.nativeElement;

    expect(htmlElement.textContent).toBe('Logout');
  });
});

为了完整性;这是我的模拟服务,MockAuthService:

import { Injectable } from '@angular/core';

@Injectable()
export class MockAuthService {
  public authState: { isAnonymous: boolean, uid: string };

  constructor() {
    this.authState = { isAnonymous: true, uid: '0HjUd9owxPZ5kibvUCN6S2DgB4x1' };
  }

  // public get currentUser(): firebase.User {
  //   return this.authState ? this.authState : undefined;
  // }

  // public get currentUserObservable(): Observable<firebase.User> {
  //   return this.afAuth.authState;
  // }

  public get currentUid(): string {
    return this.authState ? this.authState.uid : undefined;
  }

  public get isAnonymous(): boolean {
    return this.authState ? this.authState.isAnonymous : false;
  }

  public get isAuthenticated(): boolean {
    return !!this.authState;
  }

  // public logout(): void {
  //   this.afAuth.auth.signOut();
  // }

  public test(isAnonymous: boolean) {
    this.authState.isAnonymous = isAnonymous;
  }
}

我不知道如何提供模拟。

更新:

到目前为止,我已经根据答案和 cmets 更新了我的模拟测试规范。但是,我仍然遇到同样的问题。

我收到错误“AuthService”类型上不存在属性“test”。

这表明它仍然没有用模拟代替实际的authService 服务。

此外,当我添加:

public test(test: boolean): boolean {
  return test;
}

测试失败的实际服务;但不是因为上面的错误,而是因为它应该——没有满足测试的期望。

这是我更新的规范:

import { DebugElement } from '@angular/core';
import {
  async,
  inject,
  ComponentFixture,
  TestBed
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';

import { FacebookLoginComponent } from './facebook-login.component';
import { AuthService } from '../shared/auth.service';
import { MockAuthService } from '../shared/testing/auth.service';

describe('FacebookLoginComponent', () => {
  let authService: AuthService;
  let component: FacebookLoginComponent;
  let fixture: ComponentFixture<FacebookLoginComponent>;
  let debugElement: DebugElement;
  let htmlElement: HTMLElement;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ FacebookLoginComponent ],
      providers: [{ provide: AuthService, useClass: MockAuthService }]
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(FacebookLoginComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should be created', () => {
    expect(component).toBeTruthy();
  });

  it('should show the Facebook Login button', inject([ AuthService ], (service: AuthService) => {
    debugElement = fixture.debugElement.query(By.css('button'));
    htmlElement = debugElement.nativeElement;

    expect(htmlElement.textContent).toBe('Facebook Login');
  }));

  it('should show the Logout button', inject([ AuthService ], (service: AuthService) => {
    expect(service.isAnonymous).toBe(true);

    debugElement = fixture.debugElement.query(By.css('button'));
    htmlElement = debugElement.nativeElement;

    service.test(false);

    fixture.detectChanges();

    expect(htmlElement.textContent).toBe('Logout');
  }));
});

【问题讨论】:

  • 这是一个类,不是一个值; useClass: MockAuthService,或useValue: new MockAuthService()
  • 感谢@jonrsharpe,虽然我仍然遇到这个问题,但我已经更新了我的测试规范。请查看我更新的问题。
  • 如果你访问(service as MockAuthService).test会怎样?
  • 然后我得到错误Type 'AuthService' cannot be converted to type 'MockAuthService'. Property 'authState' is private in type 'AuthService' but not in type 'MockAuthService'.@jonrsharpe
  • 检查我更新的答案。

标签: angular unit-testing karma-jasmine


【解决方案1】:
beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ FacebookLoginComponent ],
      // providers: [{ provide: AuthService, useValue: MockAuthService }]
    })
      .compileComponents();

    TestBed.overrideComponent(FacebookLoginComponent, {
      set: {
        providers: [{ provide: AuthService, useClass: MockAuthService }]
      }
    })
  }));

有两个问题。 您不需要覆盖组件的提供程序,因为您可以在配置 TestBed 时提供它们。

我假设您已经开始覆盖该组件,因为初始配置不起作用。它不起作用,因为您使用的是 useValue 而不是 useClass

  // providers: [{ provide: AuthService, useValue: MockAuthService }]

应该这样做:

beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ FacebookLoginComponent ],
      providers: [{ provide: AuthService, useClass: MockAuthService }]
    }).compileComponents();
  }));

编辑:

当使用inject函数时,你应该使用MockAuthService作为类型。 TypeScript 应该停止抱怨。

inject([ AuthService ], (service: MockAuthService) => { /* ... */ });

【讨论】:

  • 感谢您的建议,但不幸的是,这似乎仍在使用原始(不是模拟)服务,请参阅我更新的问题。
【解决方案2】:

所以,我在同样的问题上挣扎并失去了理智。我的组件中有 2 个服务需要模拟以进行测试。一个(我们称之为MyService)成功地使用了一个模拟,另一个(我们称之为MyOtherService)不会使用模拟而是实际的服务。

最终我想通了(有点)。 所以这是我正在测试的类/组件:

@Component({
  selector: "app-my-component",
  templateUrl: "./my-component.component.html",
  styleUrls: ["./my-component.component.scss"],
  providers: [MyOtherService]
})
export class MyComponentComponent implements OnInit, OnDestroy {
 ...private logic...

  constructor(
    private myService: MyService,
    private myOtherService: MyOtherService,
    private route: ActivatedRoute,
  ) {}

..component logic...

看到MyOtherService 正在做的事情MyService 不是吗?..... 它在 @Component 的提供者中!

事实证明,当我在构造函数中拥有该服务时,我并不真正需要该服务,因此我将其删除,并开始使用模拟而不是实际服务进行测试。

所以,我会检查您是在构造函数中还是在 @Component 的提供者中添加服务。

现在,我认为必须有一种方法仍然可以模拟在 @Component 中的提供程序中声明的服务,但我无法弄清楚这一点,并且由于我的构造函数得到了它更容易删除它无论如何服务。

我知道你的问题已经很老了,但我希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 2017-03-14
    • 2015-12-26
    • 1970-01-01
    • 2016-06-23
    • 2017-05-10
    • 2019-05-21
    • 1970-01-01
    • 1970-01-01
    • 2017-03-12
    相关资源
    最近更新 更多