angularhttpjasmine

How to Unit Test an HTTP Service?


I have a service:

export class AuthService {
  apiURL = `${environment.apiURL}/${EndPoints.auth}`;
  constructor(private http: HttpClient, private store: Store<AppState>) {}

  loggedUser$ = this.store.select(selectIsUserLogged);

  login(body: LoginData): Observable<ResponseUser> {
    return this.http.post<ResponseUser>(
      `${this.apiURL}/${EndPoints.login}`,
      body,
      {
        withCredentials: true,
      }
    );
  }

  logout(): Observable<MessageResponse> {
    return this.http.get<MessageResponse>(
      `${this.apiURL}/${EndPoints.logout}`,
      {
        withCredentials: true,
      }
    );
  }

  register(body: RegisterData): Observable<MessageResponse> {
    return this.http.post<MessageResponse>(
      `${this.apiURL}/${EndPoints.register}`,
      body
    );
  }

  autoLogin(): Observable<ResponseUser> {
    return this.http.get<ResponseUser>(
      `${this.apiURL}/${EndPoints.autoLogin}`,
      {
        withCredentials: true,
      }
    );
  }
}

And I want to write unit test to this service. This is my code:

describe('AuthService', () => {
  let service: AuthService;
  let testingController: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule, StoreModule.forRoot({})],
      providers: [AuthService],
    });
    service = TestBed.inject(AuthService);
    testingController = TestBed.inject(HttpTestingController);
  });

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

  it('should user be register', (done) => {
    const body: RegisterData = {
      email: 'test1@mail.com',
      firstName: 'test',
      lastName: 'test',
      password: '12345678',
    };

    const messageResponse = {
      message: 'User registered successfully',
    };

    service.register(body).subscribe((response) => {
      expect(response).toBeTruthy();
    });

    const mokcReq = testingController.expectOne(
      `${environment.apiURL}/${EndPoints.auth}/${EndPoints.register}`
    );
    mokcReq.flush(messageResponse.message);
    testingController.verify();
  });
});

And I have an error: Error: Timeout - Async function did not complete within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)

I try to extend default timeout, but problem is somewhere else. Also i want to ask how to tests the others method from service. And I don't know if understand correctly, when I tests register user will be create at the database and when I run tests next time I get the error that I want to create user with the same data.


Solution

  • 1. Timeout in test

    The test is in timeout because you have set (done), but you're not calling it. Setting (done) means that your test is gonna wait for done() to be called to say that the test is successful. (done) is useful to test asynchonous code because a test can be marked as successful when it has jumped over asserts.

    Without done, if the response is slow to come, the expect may not be executed and the test would end without error even if the response would have been falsy.

    service.register(body).subscribe((response) => {
          expect(response).toBeTruthy();
        });
    

    To fix the timeout, add done() :

    it('should user be register', (done) => {
        const body: RegisterData = {
          email: 'test1@mail.com',
          firstName: 'test',
          lastName: 'test',
          password: '12345678',
        };
    
        const messageResponse = {
          message: 'User registered successfully',
        };
    
        service.register(body).subscribe((response) => {
          expect(response).toBeTruthy();
          // add done here
          done();
        });
    
        const mokcReq = testingController.expectOne(
          `${environment.apiURL}/${EndPoints.auth}/${EndPoints.register}`
        );
        mokcReq.flush(messageResponse.message);
        testingController.verify();
      });
    

    2. Unit test concept

    when I tests register user will be create at the database and when I run tests next time I get the error that I want to create user with the same data.

    These are unit tests. You are using HttpClientTestingModule, which don't do real http calls. This means that you can replay the tests because no real actions are done.

    In this piece of code, you are replying a mocked reponse messageResponse.message when ${environment.apiURL}/${EndPoints.auth}/${EndPoints.register} is called.

    const mokcReq = testingController.expectOne(
          `${environment.apiURL}/${EndPoints.auth}/${EndPoints.register}`
        );
        mokcReq.flush(messageResponse.message);
    

    3. How to tests the other methods from the service

    You can test other methods in the same way as your first test: calling the method, mocking http return, expecting http url, expecting return value (not forgetting done) etc.

    You can also check that the correct http method has been called (get/post etc)

    testingController.expectOne({
              url: `${environment.apiURL}/${EndPoints.auth}/${EndPoints.register}`,
              method: "GET"
            });
    

    or

    const mokcReq = testingController.expectOne(
              `${environment.apiURL}/${EndPoints.auth}/${EndPoints.register}`
            );
    expect(mokcReq.request.method).toEqual("GET");
    

    You can also check that the correct body has been provided is the http request

    const mokcReq = testingController.expectOne(
              `${environment.apiURL}/${EndPoints.auth}/${EndPoints.register}`
            );
    expect(mokcReq.request.body).toEqual(expectedBody);