十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
为了检查你的服务是否正常工作,你可以专门为它们编写测试。

成都创新互联公司专注于刚察网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供刚察营销型网站建设,刚察网站制作、刚察网页设计、刚察网站官网定制、微信小程序开发服务,打造刚察网络公司原创品牌,更为您提供刚察网站排名全网营销落地服务。
如果你要试验本指南中所讲的应用,请在浏览器中运行它或下载并在本地运行它。
服务往往是最容易进行单元测试的文件。下面是一些针对 ValueService 的同步和异步单元测试,甚至不需要 Angular 测试工具的帮助。
// Straight Jasmine testing without Angular's testing support
describe('ValueService', () => {
  let service: ValueService;
  beforeEach(() => { service = new ValueService(); });
  it('#getValue should return real value', () => {
    expect(service.getValue()).toBe('real value');
  });
  it('#getObservableValue should return value from observable',
    (done: DoneFn) => {
    service.getObservableValue().subscribe(value => {
      expect(value).toBe('observable value');
      done();
    });
  });
  it('#getPromiseValue should return value from a promise',
    (done: DoneFn) => {
    service.getPromiseValue().then(value => {
      expect(value).toBe('promise value');
      done();
    });
  });
});服务通常依赖于 Angular 在构造函数中注入的其它服务。在很多情况下,调用服务的构造函数时,很容易手动创建和注入这些依赖。
MasterService 就是一个简单的例子:
@Injectable()
export class MasterService {
  constructor(private valueService: ValueService) { }
  getValue() { return this.valueService.getValue(); }
}MasterService 只把它唯一的方法 getValue 委托给了所注入的 ValueService。 
这里有几种测试方法。
describe('MasterService without Angular testing support', () => {
  let masterService: MasterService;
  it('#getValue should return real value from the real service', () => {
    masterService = new MasterService(new ValueService());
    expect(masterService.getValue()).toBe('real value');
  });
  it('#getValue should return faked value from a fakeService', () => {
    masterService = new MasterService(new FakeValueService());
    expect(masterService.getValue()).toBe('faked service value');
  });
  it('#getValue should return faked value from a fake object', () => {
    const fake =  { getValue: () => 'fake value' };
    masterService = new MasterService(fake as ValueService);
    expect(masterService.getValue()).toBe('fake value');
  });
  it('#getValue should return stubbed value from a spy', () => {
    // create `getValue` spy on an object representing the ValueService
    const valueServiceSpy =
      jasmine.createSpyObj('ValueService', ['getValue']);
    // set the value to return when the `getValue` spy is called.
    const stubValue = 'stub value';
    valueServiceSpy.getValue.and.returnValue(stubValue);
    masterService = new MasterService(valueServiceSpy);
    expect(masterService.getValue())
      .withContext('service returned stub value')
      .toBe(stubValue);
    expect(valueServiceSpy.getValue.calls.count())
      .withContext('spy method was called once')
      .toBe(1);
    expect(valueServiceSpy.getValue.calls.mostRecent().returnValue)
      .toBe(stubValue);
  });
});第一个测试使用 new 创建了一个 ValueService,并把它传给了 MasterService 的构造函数。 
然而,注入真实服务很难工作良好,因为大多数被依赖的服务都很难创建和控制。
相反,可以模拟依赖、使用仿制品,或者在相关的服务方法上创建一个测试间谍。
我更喜欢用测试间谍,因为它们通常是模拟服务的最佳途径。
这些标准的测试技巧非常适合对服务进行单独测试。
但是,你几乎总是使用 Angular 依赖注入机制来将服务注入到应用类中,你应该有一些测试来体现这种使用模式。Angular 测试实用工具可以让你轻松调查这些注入服务的行为。
你的应用依靠 Angular 的依赖注入(DI)来创建服务。当服务有依赖时,DI 会查找或创建这些被依赖的服务。如果该被依赖的服务还有自己的依赖,DI 也会查找或创建它们。
作为服务的消费者,你不应该关心这些。你不应该关心构造函数参数的顺序或它们是如何创建的。
作为服务的测试人员,你至少要考虑第一层的服务依赖,但当你用 TestBed 测试实用工具来提供和创建服务时,你可以让 Angular DI 来创建服务并处理构造函数的参数顺序。
TestBed 是 Angular 测试实用工具中最重要的。TestBed 创建了一个动态构造的 Angular 测试模块,用来模拟一个 Angular 的 @NgModule。 
TestBed.configureTestingModule() 方法接受一个元数据对象,它可以拥有@NgModule的大部分属性。 
要测试某个服务,你可以在元数据属性 providers 中设置一个要测试或模拟的服务数组。
let service: ValueService;
beforeEach(() => {
  TestBed.configureTestingModule({ providers: [ValueService] });
});将服务类作为参数调用 TestBed.inject(),将它注入到测试中。
注意:
TestBed.get() 已在 Angular 9 中弃用。为了帮助减少重大变更,Angular 引入了一个名为 
TestBed.inject() 的新函数,你可以改用它。
it('should use ValueService', () => {
  service = TestBed.inject(ValueService);
  expect(service.getValue()).toBe('real value');
});或者,如果你喜欢把这个服务作为设置代码的一部分进行注入,也可以在 beforeEach() 中做。
beforeEach(() => {
  TestBed.configureTestingModule({ providers: [ValueService] });
  service = TestBed.inject(ValueService);
});测试带依赖的服务时,需要在 providers 数组中提供 mock。 
在下面的例子中,mock 是一个间谍对象。
let masterService: MasterService;
let valueServiceSpy: jasmine.SpyObj;
beforeEach(() => {
  const spy = jasmine.createSpyObj('ValueService', ['getValue']);
  TestBed.configureTestingModule({
    // Provide both the service-to-test and its (spy) dependency
    providers: [
      MasterService,
      { provide: ValueService, useValue: spy }
    ]
  });
  // Inject both the service-to-test and its (spy) dependency
  masterService = TestBed.inject(MasterService);
  valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj;
});  该测试会像以前一样使用该间谍。
it('#getValue should return stubbed value from a spy', () => {
  const stubValue = 'stub value';
  valueServiceSpy.getValue.and.returnValue(stubValue);
  expect(masterService.getValue())
    .withContext('service returned stub value')
    .toBe(stubValue);
  expect(valueServiceSpy.getValue.calls.count())
    .withContext('spy method was called once')
    .toBe(1);
  expect(valueServiceSpy.getValue.calls.mostRecent().returnValue)
    .toBe(stubValue);
});本指南中的大多数测试套件都会调用 beforeEach() 来为每一个 it() 测试设置前置条件,并依赖 TestBed 来创建类和注入服务。 
还有另一种测试,它们从不调用 beforeEach(),而是更喜欢显式地创建类,而不是使用 TestBed。 
你可以用这种风格重写 MasterService 中的一个测试。 
首先,在 setup 函数中放入可供复用的预备代码,而不用 beforeEach()。
function setup() {
  const valueServiceSpy =
    jasmine.createSpyObj('ValueService', ['getValue']);
  const stubValue = 'stub value';
  const masterService = new MasterService(valueServiceSpy);
  valueServiceSpy.getValue.and.returnValue(stubValue);
  return { masterService, stubValue, valueServiceSpy };
}setup() 函数返回一个包含测试可能引用的变量(如 masterService)的对象字面量。你并没有在 describe() 的函数体中定义半全局变量(比如 let masterService: MasterService)。 
然后,每个测试都会在第一行调用 setup(),然后继续执行那些操纵被测主体和断言期望值的步骤。
it('#getValue should return stubbed value from a spy', () => {
  const { masterService, stubValue, valueServiceSpy } = setup();
  expect(masterService.getValue())
    .withContext('service returned stub value')
    .toBe(stubValue);
  expect(valueServiceSpy.getValue.calls.count())
    .withContext('spy method was called once')
    .toBe(1);
  expect(valueServiceSpy.getValue.calls.mostRecent().returnValue)
    .toBe(stubValue);
});请注意测试如何使用解构赋值来提取它需要的设置变量。
const { masterService, stubValue, valueServiceSpy } = setup();许多开发人员都觉得这种方法比传统的 beforeEach() 风格更清晰明了。 
虽然这个测试指南遵循传统的样式,并且默认的CLI 原理图会生成带有 beforeEach() 和 TestBed 的测试文件,但你可以在自己的项目中采用这种替代方式。
对远程服务器进行 HTTP 调用的数据服务通常会注入并委托给 Angular 的 HttpClient服务进行 XHR 调用。 
你可以测试一个注入了 HttpClient 间谍的数据服务,就像测试所有带依赖的服务一样。
let httpClientSpy: jasmine.SpyObj;
let heroService: HeroService;
beforeEach(() => {
  // TODO: spy on other methods too
  httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);
  heroService = new HeroService(httpClientSpy);
});
it('should return expected heroes (HttpClient called once)', (done: DoneFn) => {
  const expectedHeroes: Hero[] =
    [{ id: 1, name: 'A' }, { id: 2, name: 'B' }];
  httpClientSpy.get.and.returnValue(asyncData(expectedHeroes));
  heroService.getHeroes().subscribe({
    next: heroes => {
      expect(heroes)
        .withContext('expected heroes')
        .toEqual(expectedHeroes);
      done();
    },
    error: done.fail
  });
  expect(httpClientSpy.get.calls.count())
    .withContext('one call')
    .toBe(1);
});
it('should return an error when the server returns a 404', (done: DoneFn) => {
  const errorResponse = new HttpErrorResponse({
    error: 'test 404 error',
    status: 404, statusText: 'Not Found'
  });
  httpClientSpy.get.and.returnValue(asyncError(errorResponse));
  heroService.getHeroes().subscribe({
    next: heroes => done.fail('expected an error, not heroes'),
    error: error  => {
      expect(error.message).toContain('test 404 error');
      done();
    }
  });
}); 
HeroService方法会返回 
Observables。你必须
订阅一个可观察对象(a)让它执行,(b)断言该方法成功或失败。
subscribe() 方法会接受成功(
next)和失败(
error)回调。确保你会同时提供
这两个回调函数,以便捕获错误。如果不这样做就会产生一个异步的、没有被捕获的可观察对象的错误,测试运行器可能会把它归因于一个完全不相关的测试。
数据服务和 HttpClient 之间的扩展交互可能比较复杂,并且难以通过间谍进行模拟。 
HttpClientTestingModule 可以让这些测试场景更易于管理。