Как имитировать приватную функцию в автоматических тестах Angular с помощью Jasmine или Jest
Я делюсь одной уловкой в день до (возможно, нет) окончания карантина COVID-19 в Швейцарии, 19 апреля 2020 года. Одиннадцать дней осталось до, надеюсь, лучших дней.
На этой неделе я добился некоторых успехов в одном из проектов моего клиента, и поэтому мне пришлось написать новые тестовые примеры. Для одного из них мне особенно пришлось имитировать частную функцию с помощью Jest.
Когда я реплицировал этот тест для цели этого сообщения в блоге, я понял, что на самом деле использую Jasmine, поскольку это набор тестов по умолчанию, используемый при создании новых приложений Ionic Angular 😁.
Вот почему сегодня я делюсь обоими решениями или как имитировать частную функцию с помощью Jasmine или Jest 😇.
Кредиты
Это сообщение в блоге Решение Jest было предоставлено Брайаном Адамсом на Stackoverflow. На Жасминовую тоже вдохновил ответ jurl на той же платформе.
Престижность им, не все герои носят плащи!
Испытательная установка
И снова я использую свой любимый API в демонстрационных целях: бесплатный DOG Api.
Тогда давайте согласимся, что наша цель - создать тест для следующего сервиса, который не делает ничего, кроме выборки случайной собаки, но обратите внимание, что я явно пометил функцию запроса как private
метод для демонстрационных целей.
import {Injectable} from '@angular/core'; import {HttpClient} from '@angular/common/http'; interface DogResponse { message: string; status: string; } @Injectable({ providedIn: 'root' }) export class DoggosService { constructor(private httpClient: HttpClient) { } async findDoggo(): Promise<string | null> { const response: DogResponse = await this.searchDoggos(); if (!response) { return null; } return response.message; } private searchDoggos(): Promise<DogResponse> { const url = 'https://dog.ceo/api/breeds/image/random'; return this.httpClient.get<DogResponse>(url).toPromise(); } }
Неудачный модульный тест
Прежде чем пытаться имитировать нашу частную функцию, я подумал, что написание неудачного теста будет хорошим началом.
import {TestBed} from '@angular/core/testing'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; import {DoggosService} from './doggos.service'; describe('DoggosService', () => { let httpTestingController: HttpTestingController; let service: DoggosService; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule] }); httpTestingController = TestBed.get(HttpTestingController); service = TestBed.get(DoggosService); }); it('should be created', () => { expect(service).toBeTruthy(); }); it('should fetch a doggo', async () => { const mockUrl = 'https://images.dog.ceo/breeds/setter-irish/n02100877_1965.jpg'; const data: string | null = await service.findDoggo(); expect(data).not.toBe(null); expect(data).toEqual(mockUrl); }); });
Поскольку мы выполняем HTTP-запрос, а не имитируем его, тест не проходит по таймауту. Вот почему наша цель - решить эту проблему, создавая имитацию над функцией private
, которая выполняет запрос.
Имитация частной функции с жасмином
Чтобы смоделировать частную функцию с помощью Jasmine, мы можем шпионить за нашей частной функцией службы searchDoggos
и использовать поддельный обратный вызов callFake
, чтобы при необходимости предоставить фиктивные данные в качестве возврата. Более того, мы также можем проверить, что наша функция была эффективно выполнена.
it('should fetch a doggo', async () => { const mockUrl = 'https://images.dog.ceo/breeds/setter-irish/n02100877_1965.jpg'; const handleSpy = spyOn(DoggosService.prototype as any, 'searchDoggos'); handleSpy.and.callFake(() => { return new Promise((resolve) => { resolve({ message: mockUrl, status: 'success' }); }); }); const data: string | null = await service.findDoggo(); expect(data).not.toBe(null); expect(data).toEqual(mockUrl); expect(handleSpy).toHaveBeenCalled(); });
Благодаря этим изменениям, теперь мы можем успешно запустить наш тест 🥳.
Имитация частной функции с помощью Jest
Решение Jest следует той же логике, что и предыдущее, за исключением того, что мы пользуемся преимуществами метода mockImplementation
для имитации частной функции.
it('should fetch a doggo', async () => { const mockUrl = 'https://images.dog.ceo/breeds/setter-irish/n02100877_1965.jpg'; const handleSpy = jest.spyOn(DoggosService.prototype as any, 'searchDoggos'); handleSpy.mockImplementation(() => { return new Promise(resolve => resolve({ message: mockUrl, status: 'success' }) ); }); const data: string | null = await service.findDoggo(); expect(data).not.toBe(null); expect(data).toEqual(mockUrl); expect(handleSpy).toHaveBeenCalled(); });
Резюме
Несмотря на то, что после обобщения это выглядит очень тривиально, мне потребовалось немного времени, чтобы найти эти решения, и я действительно благодарен, что и Брайан, и jurl опубликовали свои ответы на Stackoverflow. Надеюсь, это может когда-нибудь помочь кому-то!
Оставайся дома, будь в безопасности!
Дэйвид