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 '/api/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', () => {
      });
    

Solution

✏️ Update src/app/restaurant/restaurant.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Restaurant } from './restaurant';
import { environment } from '../../environments/environment';

export interface ResponseData<dataType> {
  data: Array<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) {
    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() {
    return this.httpClient.get<ResponseData<State>>(environment.apiUrl + '/states');
  }

  getCities(state:string) {
    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 { TestBed } from '@angular/core/testing';

import { ResponseData, RestaurantService, State, City } from './restaurant.service';
import { Restaurant } from './restaurant';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe('RestaurantService', () => {
  let httpMock : HttpTestingController;
  let restaurantService: RestaurantService;

  beforeEach(() => TestBed.configureTestingModule({
    imports: [HttpClientTestingModule],
    providers: [
      RestaurantService
    ]
  }));

  beforeEach(() => {
    httpMock = TestBed.get(HttpTestingController);
    restaurantService = TestBed.get(RestaurantService);
  })

  it('should be created', () => {
    expect(restaurantService).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"
            }]
          };

    restaurantService.getRestaurants("IL","Chicago").subscribe((restaurants:ResponseData<Restaurant>) => {
      expect(restaurants).toEqual(mockRestaurants);
    });

    let 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', () => {
    let 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'}
      ]
    };

    restaurantService.getStates().subscribe((states: ResponseData<State>) => {
      expect(states).toEqual(mockStates);
    });

    let 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'}
      ]
    };

    restaurantService.getCities('MO').subscribe((cities: ResponseData<City>) => {
      expect(cities).toEqual(mockCities);
    });

    let 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"
    };

    restaurantService.getRestaurant("brunch-place").subscribe((restaurant:Restaurant) => {
      expect(restaurant).toEqual(mockRestaurant);
    });

    let url = 'http://localhost:7070/restaurants/brunch-place';
    const req = httpMock.expectOne(url);


    expect(req.request.method).toEqual('GET');
    req.flush(mockRestaurant);

    httpMock.verify();
  });

});