Search code examples
angularjasmineangular-test

Error: <toHaveBeenCalled> : Expected a spy, but got Object({ authUser: spy on AuthService.authUser }). Usage: expect(<spyObj>).toHaveBeenCalled()


I'm trying to create a mock service so that after clicking on the form button, the click handler is called and subsequently a method from my mock service is called. What could be wrong here? Just started to deal with Jasmine.

My test:

describe('Login submit', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
beforeEach(async () => {
    TestBed.configureTestingModule({
      providers: [
        AuthService,
        HttpClientTestingModule,
        HttpClientModule,
        HttpClient,
        HttpHandler,
        MatSnackBar,
        Overlay,
        FormBuilder,
      ],
    });
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
 it('authService should launch', () => {
    let mockAuthService = jasmine.createSpyObj('AuthService', ['authUser']);
    const mockUser = { usernsme: 'Marcus', password: 'Marcus#1' };
    const compiled = fixture.debugElement;
    const loginBtn = compiled.query(By.css('.login-btn')).nativeElement;
    loginBtn.dispatchEvent(new Event('click'));
    expect(mockAuthService).toHaveBeenCalled();
  });
});

I made a test before this, where I manually pass the user into the method, but now I need everything to work on the button

function from login component:

userLoginClick() {
    if (!this.form.valid) {
      this.pushNotificationService.createNotification(
        'Data format is incorrect'
      );

      return false;
    }
    const user = {
      username: this.form.controls['username'].value,
      password: this.form.controls['password'].value,
    };

    this.authService.authUser(user);
  }

function from auth service:

authUser(user: User) {
    return this.http
      .post(
        `${environment.domain}${BackendRoutes.Login}`,
        user,
        this.httpOptions
      )
      .subscribe((data: any) => {
        if (!data.success) {
          this.pushNotificationService.createNotification(data.message);
        } else {
          this.router.navigate([`/${Paths.Lobby}`]);
          this.storeUser(data.token, data.user);
        }
      });
  }

Solution

  • Make the following changes, follow the comments with '!!' and explanations.

    describe('Login submit', () => {
      let component: LoginComponent;
      let fixture: ComponentFixture<LoginComponent>;
      // !! Create mockAuthService here !!
      let mockAuthService: jasmine.SpyObj<AuthService>;
    beforeEach(async () => {
        // !! Create mock in a before each so we have a fresh mock for each test.
        mockAuthService = jasmine.createSpyObj<AuthService>('AuthService', ['authUser']);
        TestBed.configureTestingModule({
          providers: [
            // !! Provide mocked AuthService instead of the real one for the tests
            { provide: AuthService, useValue: mockAuthService },
            HttpClientTestingModule,
            HttpClientModule,
            HttpClient,
            HttpHandler,
            MatSnackBar,
            Overlay,
            FormBuilder,
          ],
        });
        fixture = TestBed.createComponent(LoginComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
     it('authService should launch', () => {
        // !! Get rid of this line, we don't need it anymore.
        // let mockAuthService = jasmine.createSpyObj('AuthService', ['authUser']);
        const mockUser = { usernsme: 'Marcus', password: 'Marcus#1' };
        const compiled = fixture.debugElement;
        const loginBtn = compiled.query(By.css('.login-btn')).nativeElement;
        loginBtn.dispatchEvent(new Event('click'));
        // !! Change this line to mockAuthService.authUser (it should be the method and not the object).
        expect(mockAuthService.authUser).toHaveBeenCalled();
      });
    });
    

    This is a good resource in learning unit testing with Angular: https://testing-angular.com/.