Writing Unit Tests page
Write a unit test for a service in Angular
Overview
In this part, we will:
- Write a new getRestaurant method on our RestaurantsService
- Write a unit test for the getRestaurant method
Problem
In the next section we're going to be creating a restaurant detail view. We'll need to have a method on our service that returns one restaurant from the list. Write this method and call it getRestaurant
. It should take a string param "slug" and make a get request to the path '/restaurants/slug-here'
. Then write a unit test for this method ensuring it makes the correct request and returns an object type of Restaurant
.
What You Need to Know
How to write a method on a service that takes a param. You've learned this in previous sections.
How to write a unit test. Here's a codeblock to get you started:
it('should make a get request to get a restaurant based on its slug', () => { });
Refer to the existing tests on the service to see how to use
HttpMock
to testHttpClient
calls.
Solution
Click to see the solution
✏️ Update src/app/restaurant/restaurant.service.tsimport { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { Restaurant } from './restaurant';
export interface ResponseData<DataType> {
data: DataType[];
}
export interface State {
name: string;
short: string;
}
export interface City {
name: string;
state: string;
}
@Injectable({
providedIn: 'root',
})
export class RestaurantService {
constructor(private httpClient: HttpClient) {}
getRestaurants(
state: string,
city: string
): Observable<ResponseData<Restaurant>> {
const params = new HttpParams()
.set('filter[address.state]', state)
.set('filter[address.city]', city);
return this.httpClient.get<ResponseData<Restaurant>>(
environment.apiUrl + '/restaurants',
{ params }
);
}
getStates(): Observable<ResponseData<State>> {
return this.httpClient.get<ResponseData<State>>(
environment.apiUrl + '/states'
);
}
getCities(state: string): Observable<ResponseData<City>> {
const params = new HttpParams().set('state', state);
return this.httpClient.get<ResponseData<City>>(
environment.apiUrl + '/cities',
{ params }
);
}
getRestaurant(slug: string) {
return this.httpClient.get<Restaurant>(
environment.apiUrl + '/restaurants/' + slug
);
}
}
✏️ Update src/app/restaurant/restaurant.service.spec.ts
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { Restaurant } from './restaurant';
import {
City,
ResponseData,
RestaurantService,
State,
} from './restaurant.service';
describe('RestaurantService', () => {
let httpMock: HttpTestingController;
let service: RestaurantService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
});
httpMock = TestBed.inject(HttpTestingController);
service = TestBed.inject(RestaurantService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should make a get request to restaurants', () => {
const mockRestaurants = {
data: [
{
name: 'Brunch Place',
slug: 'brunch-place',
images: {
thumbnail:
'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
owner: 'node_modules/place-my-order-assets/images/2-owner.jpg',
banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
},
menu: {
lunch: [
{ name: 'Ricotta Gnocchi', price: 15.99 },
{ name: 'Garlic Fries', price: 15.99 },
{ name: 'Charred Octopus', price: 25.99 },
],
dinner: [
{ name: 'Steamed Mussels', price: 21.99 },
{ name: 'Roasted Salmon', price: 23.99 },
{ name: 'Crab Pancakes with Sorrel Syrup', price: 35.99 },
],
},
address: {
street: '2451 W Washburne Ave',
city: 'Ann Arbor',
state: 'MI',
zip: '53295',
},
_id: 'xugqxQIX5rPJTLBv',
},
{
name: 'Taco Joint',
slug: 'taco-joint',
images: {
thumbnail:
'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
owner: 'node_modules/place-my-order-assets/images/2-owner.jpg',
banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
},
menu: {
lunch: [
{ name: 'Beef Tacos', price: 15.99 },
{ name: 'Chicken Tacos', price: 15.99 },
{ name: 'Guacamole', price: 25.99 },
],
dinner: [
{ name: 'Shrimp Tacos', price: 21.99 },
{ name: 'Chicken Enchilada', price: 23.99 },
{ name: 'Elotes', price: 35.99 },
],
},
address: {
street: '13 N 21st St',
city: 'Chicago',
state: 'IL',
zip: '53295',
},
_id: 'xugqxQIX5dfgdgTLBv',
},
],
};
service
.getRestaurants('IL', 'Chicago')
.subscribe((restaurants: ResponseData<Restaurant>) => {
expect(restaurants).toEqual(mockRestaurants);
});
const url =
'http://localhost:7070/restaurants?filter%5Baddress.state%5D=IL&filter%5Baddress.city%5D=Chicago';
// url parses to 'http://localhost:7070/restaurants?filter[address.state]=IL&filter[address.city]=Chicago'
const req = httpMock.expectOne(url);
expect(req.request.method).toEqual('GET');
req.flush(mockRestaurants);
httpMock.verify();
});
it('can set proper properties on restaurant type', () => {
const restaurant: Restaurant = {
name: 'Taco Joint',
slug: 'taco-joint',
images: {
thumbnail: 'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
owner: 'node_modules/place-my-order-assets/images/2-owner.jpg',
banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
},
menu: {
lunch: [
{ name: 'Beef Tacos', price: 15.99 },
{ name: 'Chicken Tacos', price: 15.99 },
{ name: 'Guacamole', price: 25.99 },
],
dinner: [
{ name: 'Shrimp Tacos', price: 21.99 },
{ name: 'Chicken Enchilada', price: 23.99 },
{ name: 'Elotes', price: 35.99 },
],
},
address: {
street: '13 N 21st St',
city: 'Chicago',
state: 'IL',
zip: '53295',
},
_id: 'xugqxQIX5dfgdgTLBv',
};
// will error if interface isn't implemented correctly
expect(true).toBe(true);
});
it('should make a get request to states', () => {
const mockStates = {
data: [{ name: 'Missouri', short: 'MO' }],
};
service.getStates().subscribe((states: ResponseData<State>) => {
expect(states).toEqual(mockStates);
});
const url = 'http://localhost:7070/states';
const req = httpMock.expectOne(url);
expect(req.request.method).toEqual('GET');
req.flush(mockStates);
httpMock.verify();
});
it('should make a get request to cities', () => {
const mockCities = {
data: [{ name: 'Kansas City', state: 'MO' }],
};
service.getCities('MO').subscribe((cities: ResponseData<City>) => {
expect(cities).toEqual(mockCities);
});
const url = 'http://localhost:7070/cities?state=MO';
const req = httpMock.expectOne(url);
expect(req.request.method).toEqual('GET');
req.flush(mockCities);
httpMock.verify();
});
it('should make a get request to get a restaurant based on its slug', () => {
const mockRestaurant = {
name: 'Brunch Place',
slug: 'brunch-place',
images: {
thumbnail: 'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
owner: 'node_modules/place-my-order-assets/images/2-owner.jpg',
banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
},
menu: {
lunch: [
{ name: 'Ricotta Gnocchi', price: 15.99 },
{ name: 'Garlic Fries', price: 15.99 },
{ name: 'Charred Octopus', price: 25.99 },
],
dinner: [
{ name: 'Steamed Mussels', price: 21.99 },
{ name: 'Roasted Salmon', price: 23.99 },
{ name: 'Crab Pancakes with Sorrel Syrup', price: 35.99 },
],
},
address: {
street: '2451 W Washburne Ave',
city: 'Ann Arbor',
state: 'MI',
zip: '53295',
},
_id: 'xugqxQIX5rPJTLBv',
};
service
.getRestaurant('brunch-place')
.subscribe((restaurant: Restaurant) => {
expect(restaurant).toEqual(mockRestaurant);
});
const url = 'http://localhost:7070/restaurants/brunch-place';
const req = httpMock.expectOne(url);
expect(req.request.method).toEqual('GET');
req.flush(mockRestaurant);
httpMock.verify();
});
});