Angular 9 - 如何通过模拟路由器来测试调用 navigateByUrl 的服务

Angular 9 - how to test a service that calls navigateByUrl by mocking the router

我能找到的所有示例仅展示了如何测试调用 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 来自被嘲笑的 class):

  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) 是否重要,因为它确实测试了服务中的方法?

  3. 我是否需要测试这个方法,因为它实际上依赖于其他开发人员已经测试过的其他东西(即 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)

这看起来很奇怪。

如果有人能够解释为什么使用实际路由器和导航不起作用,我将非常感激!

当我需要测试使用路由器的东西时,我会尽可能多地模拟。测试时路由总是很棘手。当我查看您的服务时,它会做一些非常简单的事情,并且无论导航如何都可以进行测试。所以我会按如下方式测试它

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 在导航时已正确设置。否则,我们的测试会变得太复杂而无法编写和维护。

回答你的另一个问题,为什么测试总是通过

运行 it 中的异步代码可能会导致意外行为。您的测试总是通过,因为测试在异步函数执行之前完成。因此,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 
      });
    });
});

有关测试异步函数的更多信息,您可以阅读here