Home > Software engineering >  Error: <toHaveBeenCalled> : Expected a spy, but got Object({ authUser: spy on AuthService.auth
Error: <toHaveBeenCalled> : Expected a spy, but got Object({ authUser: spy on AuthService.auth

Time:11-05

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

CodePudding user response:

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

  • Related