Photo by Nguyen Dang Hoang Nhu on Unsplash
In a previous post, we looked at how to setup an HttpInterceptor to add the Bearer token to the HTTP Authorization Header, when that token is stored in state with NgRx. In this post, we’ll look at how to unit test that interceptor with Jasmine/Karma.
Setting up beforeEach
Modify the beforeEach method of the test file as follows:
let httpTestingController: HttpTestingController;
let httpClient: HttpClient;
let appConfig: AppConfig;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
AuthInterceptor,
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: APP_CONFIG, useValue: { apiHost: '' } },
provideMockStore({
initialState: { user: { currentUser: user } },
}),
],
});
httpTestingController = TestBed.inject(HttpTestingController);
httpClient = TestBed.inject(HttpClient);
appConfig = TestBed.inject(APP_CONFIG);
});
Unsurprisingly, the beforeEach method executes before each test runs. The first thing we do is configure the testing module. Tests run in isolation, so the setup we provide in the AppModule (or any other module) does not get applied here. So we have to configure the testing module, just like we have to configure the AppModule for our application. Let’s break this down.
We import the HttpClientTestingModule, so that all HTTP requests will hit the HttpClientTestingBackend, instead of our real API. We don’t want to make actual requests to a real API in a unit test.
imports: [HttpClientTestingModule],
Next we provide the AuthInterceptor to our testing module and specify that it should be used in the request pipeline by providing it for the HTTP_INTERCEPTORS injection token. This means that any HTTP request (even our fake ones) will go through our interceptor. My implementation also depends on an APP_CONFIG token so I have to provide a value for that, which contains the apiHost. This is not a real API host — we just need a constant value we can compare against.
AuthInterceptor,
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: APP_CONFIG, useValue: { apiHost: 'https://mockhost/api' } },
The last thing we need to do in the providers array is call a helper method that NgRx supplies, which adds all of the providers necessary to mock the NgRx Store: provideMockStore(). If you have initial state that needs to be set for every test, you can provide that initial state to this method, as we’ve done in the code snippet above.
provideMockStore({
initialState: { user: { currentUser: user } },
}),
Alternatively, you can set/modify state in the test itself, by injecting the MockStore class and calling setState(). We don’t need to do that here, but this is what that would look like:
...
store = TestBed.inject(MockStore);
...
store.setState({ user: { currentUser: user } });
Lastly, we inject the dependencies that we need, to make them available for each test. Importing the HttpClientTestingModule is enough to make HTTP requests go to a fake backend, but the HttpTestingController class is needed to inspect those request and to return mock data, if needed.
The HttpClient is still needed and used, just like it is in the real application. The difference here is that we’ve told Angular to use a test HttpBackend, by importing the HttpClientTestingModule.
httpTestingController = TestBed.inject(HttpTestingController);
httpClient = TestBed.inject(HttpClient);
appConfig = TestBed.inject(APP_CONFIG);
The Test
Create the following test to verify that the Authorization header will be added to any HTTP request made to the appConfig.apiHost:
it('should add auth headers for calls to appConfig.apiHost', () => {
//arrange
const url = `${appConfig.apiHost}/mockendpoint`;
//act
httpClient.get(url).subscribe();
// assert
const req = httpTestingController.expectOne(url);
expect(req.request.headers.get('Authorization'))
.toEqual(`Bearer ${user.authToken}`);
});
First we arrange the test, just by specifying a URL to call. The only important piece here is the appConfig.apiHost. The rest can be anything and does not actually exist anywhere.
const url = `${appConfig.apiHost}/mockendpoint`;
Next we make the call to that URL, but remember it’s not actually going anywhere across the wire.
httpClient.get(url).subscribe();
Finally, we get the fake request, using the HttpTestingController, and verify that the Authorization Header has been set with the user’s Bearer auth token.
const req = httpTestingController.expectOne(url);
expect(req.request.headers.get('Authorization'))
.toEqual(`Bearer ${user.authToken}`);
Now we have a test to ensure that our Authorization Header will be added to every HTTP request to the appConfig.apiHost, and that the value of the Header will be ‘Bearer’ plus the user’s Bearer token.
We would also want to add a test to make sure that the opposite is true — that calls not to appConfig.apiHost do not have the Header added. I’ll include that test here for reference, but I don’t think it requires any additional explanation:
it('should not add auth headers for calls that are not to appConfig.apiHost', () => {
//arrange
const url = `https://someotherurl/mockendpoint`;
//act
httpClient.get(url).subscribe();
// assert
const req = httpTestingController.expectOne(url);
expect(req.request.headers.has('Authorization')).toBe(false);
});
That’s it for this one. Hope you find it useful.
Bibliography
- https://medium.com/@seanhaddock_60973/how-to-add-a-bearer-token-to-api-calls-when-using-ngrx-317f35fbb6f2
- https://angular.io/api/common/http/testing/HttpClientTestingModule
- https://ngrx.io/api/store/testing/provideMockStore
- https://ngrx.io/api/store/testing/MockStore
- https://angular.io/api/common/http/testing/HttpTestingController
- https://angular.io/api/common/http/HttpBackend
- https://angular.io/guide/http-test-requests
Top comments (0)