Angular 9 - как протестировать сервис, который вызывает navigateByUrl, имитируя маршрутизатор - PullRequest
1 голос
/ 07 мая 2020

Все примеры, которые я могу найти, показывают только то, как тестировать компонент, который вызывает navigateByUrl. У меня есть служба, которая это делает:

@Injectable({
  providedIn: 'root'
})
export class BreadcrumbService {
  constructor(private router: Router, private activateRoute: ActivatedRoute) {}

  /**
   * Return a array of current path
   *
   */
  getCurrentRouterPath(): Array<string> {
    return this.router.url.split('/').filter(path => path);
  }
}

Я пытаюсь проверить это, издевавшись над маршрутизатором:

import {TestBed, async, inject} from '@angular/core/testing';
import {BreadcrumbService} from './breadcrumb.service';
import {ActivatedRoute, Router} from '@angular/router';

class MockRouter {
  navigateByUrl(url: string) {
    return { url };
  }
}

describe('BreadcrumbService', () => {
  let service: BreadcrumbService;
  let activateRoute: ActivatedRoute;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      providers: [
        { provide: Router, useClass: MockRouter },
        { provide: ActivatedRoute }
    ]
    });
    activateRoute = TestBed.inject(ActivatedRoute);
    service = TestBed.inject(BreadcrumbService);
  }));

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should return an array of route path', inject([Router], (router: MockRouter) => {
      const spy = spyOn(router, 'navigateByUrl');
      router.navigateByUrl('/giveaway/all');
      const arrayPath = service.getCurrentRouterPath();
      expect(arrayPath).not.toBeNull();
      expect(arrayPath).not.toBeUndefined();
      expect(arrayPath).toEqual(['giveaway', 'all']);    
  }));
});

... но URL-адрес всегда не определен, поэтому cannot read property 'split of undefined occurs.

Что я пробовал

Поэтому я решил имитировать само свойство url, чтобы избежать этого:

class MockRouter {
  private _url = '';
  get url(): string {
      return this._url;
  }
  set url(value: string) {
      this._url = value;
  }
}

Теперь тест выполняется, но свойство url всегда пусто, поэтому я возвращаю пустой массив, поэтому тест не проходит.

Хорошо, поэтому я могу выполнить этот проход, явно установив URL-адрес непосредственно в макете, но мне это кажется неправильным так как он полностью пропускает этап навигации (как вы заметили, я удалил navigateByUrl из имитируемого класса):

  it('should return an array of route path', inject([Router], (router: MockRouter) => {
      router.url = '/giveaway/all';
      const arrayPath = service.getCurrentRouterPath();
      expect(arrayPath).not.toBeNull();
      expect(arrayPath).not.toBeUndefined();
      expect(arrayPath).toEqual(['giveaway', 'all']);    
  }));

Итак, здесь действительно есть 3 вопроса:

  1. НЕ должен ли я издеваться над маршрутизатором, а фактически вводить настоящий маршрутизатор и фактически запускать событие навигации в тесте? (пробовал это, ничего не получается! - см. примечание ниже)

  2. Имеет ли значение (1), если я просто go с тем, что работает выше, поскольку он действительно тестирует метод в service?

  3. Нужно ли мне вообще тестировать этот метод, поскольку все, от чего он действительно зависит, - это другие вещи, которые уже проверены другими разработчиками (ie маршрутизатор Angular, массив разделить и отфильтровать).

ПРИМЕЧАНИЕ

Я унаследовал код, и изначально он был таким, как показано ниже, где тест никогда не терпит неудачу:

import {TestBed} from '@angular/core/testing';
import {BreadcrumbService} from './breadcrumb.service';
import {RouterTestingModule} from '@angular/router/testing';
import {ActivatedRoute, Router} from '@angular/router';
import {NgZone} from '@angular/core';

describe('BreadcrumbService', () => {
  let service: BreadcrumbService;
  let router: Router;
  let activateRoute: ActivatedRoute;
  let ngZone: NgZone;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule.withRoutes([])]
    });
    router = TestBed.inject(Router);
    activateRoute = TestBed.inject(ActivatedRoute);
    service = TestBed.inject(BreadcrumbService);
    ngZone = TestBed.inject(NgZone);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should receive a array of route path', () => {
    ngZone.run(() => {
      router.navigateByUrl('/giveaway/all').then(() => {
        const arrayPath = service.getCurrentRouterPath();
        expect(arrayPath).not.toBeNull();
        expect(arrayPath).not.toBeUndefined();
        expect(arrayPath.length).toEqual(2);
      });
    });
  });
});

который всегда проходит независимо от длины вызываемой вами навигации или проверки равенства. Таким образом, даже это проходит:

        expect(arrayPath.length).toEqual(200)

и это:

        expect(arrayPath.length).toEqual(0)

, что кажется очень странным.

Если кто-нибудь может объяснить, почему используется настоящий маршрутизатор и навигация не вышло Буду безмерно признателен!

1 Ответ

1 голос
/ 10 мая 2020

Когда мне нужно протестировать что-то, что использует роутер, я стараюсь как можно больше высмеивать. При тестировании маршрутизация всегда непроста. Когда я смотрю на ваш сервис, он делает что-то очень простое и может быть протестировано независимо от навигации. Поэтому я бы просто протестировал его следующим образом:

const testUrl = '/some/test/url';

beforeEach(async(() => {
    TestBed.configureTestingModule({
      providers: [
        { 
          provide: Router, 
          useValue: {
            url: testUrl 
          } 
        }
    ]
    });
    activateRoute = TestBed.inject(ActivatedRoute);
    service = TestBed.inject(BreadcrumbService);
}));

it('should return an array of route path', () => {
  const arrayPath = service.getCurrentRouterPath();
  expect(arrayPath).not.toBeNull();
  expect(arrayPath).not.toBeUndefined();
  expect(arrayPath).toEqual(['some', 'test', 'url']);    
}));

Ваш тест не должен заботиться о том, как установлен url. Это чужая работа. Вы должны предположить, что Router хорошо протестирован, а url правильно настроен при навигации. В противном случае наши тесты станут слишком сложными для написания и сопровождения.

Чтобы ответить на ваш другой вопрос, почему тест всегда проходит

Запуск asyn c кода в it может привести к неожиданному поведению . Ваш тест всегда проходит успешно, потому что он завершается до выполнения функции asyn c. Итак, expect.... выполняется, когда тест уже выполнен (и пройден). Чтобы провести тест до тех пор, пока вы не запустите свой код, вы можете использовать done следующим образом

it('should receive a array of route path', (done) => {
    ngZone.run(() => {
      router.navigateByUrl('/giveaway/all').then(() => {
        const arrayPath = service.getCurrentRouterPath();
        expect(arrayPath).not.toBeNull();
        expect(arrayPath).not.toBeUndefined();
        expect(arrayPath.length).toEqual(2);

        done(); // mark the test as done 
      });
    });
});

Для получения дополнительной информации о тестировании асинхронных c функций вы можете прочитать его здесь

...