简体   繁体   中英

Angular2 Unit Test Not Using Mock Service

I am testing a very simple component that shows/hides the login/out buttons.

For this I am mocking my AuthService service as that relies AngularFire2.

The problem I am having is that is appears my mock service ( Mock AuthService ) isn't being provided in place of the actual AuthService .

In the test should show the Facebook login button , service.isAnonymous is expected to be undefined. In the actual service, it is. But in the mock service is is true . This test should fail.

Also, notice I am trying to call the method service.test(false); ; in the mock service this method exists and is public . But I receive the error:

Property 'test' does not exist on type 'AuthService'.

This suggests that my mock service is not being provided.

You can see how I have tried two way to provide the mock service (one is commented out) in my test spec:

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');
  });
});

For completeness; here is my mock service, 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;
  }
}

I am at a loss as to how to provide the mock instead.

Update:

Based on answers and comments so far I have update my mock test spec. However, I am still having the same issue.

I get the error Property 'test' does not exist on type 'AuthService'.

This suggests it is still not substituting the mock for the actual authService service.

Furthermore, when I add:

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

To the actual service the test fails; but not because of the error above, but because it should — the expectations of the test are not met.

This is my updated spec:

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');
  }));
});
beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ FacebookLoginComponent ],
      // providers: [{ provide: AuthService, useValue: MockAuthService }]
    })
      .compileComponents();

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

There are two problems. You don't need to override component's providers because you can provide them when configuring the TestBed .

I assume that you've started overriding the component because the initial configuration didn't work. It didn't work because you've used useValue instead of useClass .

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

This should do:

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

EDIT:

When using inject function, you should use MockAuthService as a type. TypeScript should stop complaining.

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

So, I was struggling and losing my mind over this same issue. I had 2 services in my component I needed to mock for testing. One (let's call it MyService ) was successfully using a mock, and the other (let's call MyOtherService ) wouldn't use the mock but the actual service.

Eventually I figured it out (kinda). So this is the class/component I was testing:

@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...

See something that MyOtherService is doing that MyService isn't?..... It's in the providers in the @Component !

Turns out I didn't really need that service there when I had it in my constructor, so I removed it and the test started using the mock instead of the actual service.

So, I would check if you're adding services in constructor or in the providers in the @Component .

Now, I think there must be a way to still mock services that are declared in the providers in the @Component , but I wasn't able to figure that out and it was easier to remove it since my constructor was getting the service anyway.

I know your question is very old but I hope this helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM