Logo
Published on

How to Unit Test Error Response Handling in Angular

Authors
  • Name
    Twitter

Photo by James Harrison on Unsplash

In this post we’ll quickly see how to mock an error response in order to test that an HttpInterceptor is redirecting to a login page when a 401 status code is returned.

Testing the Redirect

First, in order to test a redirect, we create a redirect method:

redirect(href: string) {
// don't redirect if localhost:9876 (for unit tests)
if (window.location.href.indexOf('http://localhost:9876') === -1) {
window.location.href = href;
}
}

This is so that we can monitor when a redirect is requested and also not actually redirect during the unit test. You would need to change the port to whatever you are using, but 9876 is the default for Karma.

Testing for the Error Response

Now we need to setup the beforeEach for the tests:

const redirectFunctionName = 'redirect'
const user = getTestUser()
let httpTestingController: HttpTestingController
let httpClient: HttpClient
let appConfig: AppConfig
let globalErrorHandlerService: GlobalErrorHandlerService

beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [HttpClientTestingModule],
    providers: [
      AuthInterceptor,
      { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
      { provide: APP_CONFIG, useValue: { apiHost: 'https://mockhost/api' } },
      provideMockStore({
        initialState: { user: { currentUser: user } },
      }),
    ],
  })
  httpTestingController = TestBed.inject(HttpTestingController)
  httpClient = TestBed.inject(HttpClient)
  appConfig = TestBed.inject(APP_CONFIG)
  globalErrorHandlerService = TestBed.inject(GlobalErrorHandlerService)
})

I’ve covered most of this code in detail in a previous post. The only significant change here is the injecting of the GlobalErrorHandlerService, which is just the service where I put the redirect method.

Now we implement the test to see if the redirect method is called when a 401 response is received.

First we arrange the test by setting the URL and a creating a spy for the GlobalErrorHandlerService, so that we can check whether or not the redirect method was called.

it('should redirect to login when 401 response received', () => {
  //arrange
  const url = `${appConfig.apiHost}/mockendpoint`
  const redirectSpy = spyOn(globalErrorHandlerService, redirectFunctionName)

  //act

  //assert
})

Next we perform the action to be tested. We make the call using httpClient.get, but it’s not actually going anywhere across the wire because we imported the HttpClientTestingModule, so that all HTTP requests will hit the HttpClientTestingBackend.

//act
httpClient.get(url).subscribe()

Now comes the important part and the crux of this post, which is simulating the error response.

By calling httpTestingController.expectOne(url), we are doing two things. We are getting the mock test request while also setting the expectation that the (mock) call will be made exactly one time. If it is not made exactly one time the test will fail.

const mockRequest = httpTestingController.expectOne(url);

Now that we have the mock test request we can simulate a response. This can be done with the flush method or in our case, the error method, since the error is all we are interested in.

The error method expects a ProgressEvent which takes a type argument which is not important for our purposes. We’re interested in the second argument to the error method which is of type TestRequestOptions. In those options we can specify that the status of the response is 401.

mockRequest.error(new  ProgressEvent('error'), { status: 401 });

Finally, we assert using our spy that the redirect method was called with the URL to the login page, which is where we want to direct our users when we receive a 401.

// assert
expect(redirectSpy).toHaveBeenCalledWith('/login')

Here’s the test in full:

//arrange
const url = `${appConfig.apiHost}/mockendpoint`
const redirectSpy = spyOn(globalErrorHandlerService, redirectFunctionName)

//act
httpClient.get(url).subscribe()

const mockRequest = httpTestingController.expectOne(url)
mockRequest.error(new ProgressEvent('error'), { status: 401 })

// assert
expect(redirectSpy).toHaveBeenCalledWith('/login')

Finally we add an afterEach function to verify that there are no outstanding requests after each test.

afterEach(() => {
  httpTestingController.verify()
})

I’ll leave as homework adding a test to make sure the redirect method does not get called if the status is not 401.

That’s it! I hope you found this useful.

Bibliography