【问题标题】:Test Angular2 service with mock backend使用模拟后端测试 Angular2 服务
【发布时间】:2015-12-26 02:15:38
【问题描述】:

首先:我知道 Angular2 处于 alpha 阶段并且经常更改。

我正在使用 Angular2。有一个具有 http 依赖项的可注入服务,我想使用模拟后端进行测试。该服务在应用程序启动时工作,但我没有运气编写测试并让模拟后端做出响应。任何见解,我在测试设置或实施中是否缺少明显的东西?

服务/core.ts:

import { Injectable } from 'angular2/angular2';
import { Http } from 'angular2/http';

@Injectable()
export class CoreService {

    constructor(public http:Http) {}

    getStatus() {
        return this.http.get('/api/status')
            .toRx()
            .map(res => res.json());
    }
}

服务/core_spec.ts:

import {
    AsyncTestCompleter,
    TestComponentBuilder,
    By,
    beforeEach,
    ddescribe,
    describe,
    el,
    expect,
    iit,
    inject,
    it,
    xit
} from 'angular2/test';
import { MockBackend, MockConnection, BaseRequestOptions, Http, Response } from 'angular2/http';
import { Injector, bind } from 'angular2/angular2';
import { ObservableWrapper } from 'angular2/src/core/facade/async'

import { CoreService } from 'public/services/core'

export function main() {

    describe('public/services/core', () => {

        let backend: MockBackend;
        let response: Response;
        let coreService: CoreService;
        let injector: Injector;

        afterEach(() => backend.verifyNoPendingRequests());

        it('should get status', inject([AsyncTestCompleter], (async) => {

            injector = Injector.resolveAndCreate([
                BaseRequestOptions,
                MockBackend,
                bind(Http).toFactory((backend, options) => {
                    return new Http(backend, options)
                }, [MockBackend, BaseRequestOptions]),
                bind(CoreService).toFactory((http) => {
                    return new CoreService(http);
                }, [Http])
            ]);

            backend = injector.get(MockBackend);
            coreService = injector.get(CoreService);
            response = new Response('foo');

            ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
                expect(c.request.url).toBe('/api/status');
                c.mockRespond(response);
            });

            // attempt #1: fails because res argument is undefined
            coreService.getStatus().subscribe(res => {
                expect(res).toBe('');
                async.done();
            });

            // attempt #2: fails because emitter.observer is not a function
            ObservableWrapper.subscribe(coreService.getStatus(), res => {
                expect(res).toBe('');
                async.done();
            });

        }));
    });

}

相关: https://github.com/angular/angular/issues/3502 https://github.com/angular/angular/issues/3530

【问题讨论】:

  • 你有什么错误吗?它到底在做什么?
  • 我将错误包含在内。直接订阅 (coreService.getStatus().subscribe(...)) 会收到错误,res 参数返回为未定义。如果我使用 ObservableWrapper.subscribe(...),它会抛出一个错误,指出emitter.observable 不是一个函数。感谢您的提问。
  • 如果将from 'angular2/test' 更改为from 'angular2/test_lib' 会发生什么?有用吗?
  • 当您测试请求 url 时,您不会检查它是否是 GET、POST、DELETE 等。只是 url 字符串。这和我现在正在做的一样。我不知道如何测试请求的类型。在 Angular 1 中,我们有类似 $httpBackend.expectGET('api/status') 的东西,你知道我们如何测试它吗?谢谢
  • 第一行不应该是 from 'angular2/core' 而不是 from 'angular2/angular2' 吗?当我尝试执行后者时,我的编译器会抱怨。

标签: http dependency-injection typescript angular


【解决方案1】:

我只是在寻找测试技巧时发现了这个主题,但我看不到直接的答案,所以......

这个基于Angular RC.1

使用模拟后端测试服务

假设你的服务是:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

@Injectable()
export class CoreService {
  constructor(private http: Http) {}

  getStatus() {
    return this.http.get('/api/status');
  }
}

对上述服务的测试将如下所示:

import {
  beforeEach,
  beforeEachProviders,
  describe,
  expect,
  inject,
  it,
} from '@angular/core/testing';

import { provide } from '@angular/core';
import { BaseRequestOptions, Response, ResponseOptions } from '@angular/http';
import { MockBackend, MockConnection } from '@angular/http/testing';

describe('Http', () => {

  beforeEachProviders(() => [
    CoreService,
    BaseRequestOptions,
    MockBackend,
    provide(Http, {
      useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
        return new Http(backend, defaultOptions);
      },
      deps: [MockBackend, BaseRequestOptions]
    })
  ]);

  beforeEach(inject([MockBackend], (backend: MockBackend) => {
    const baseResponse = new Response(new ResponseOptions({ body: 'status' }));
    backend.connections.subscribe((c: MockConnection) => c.mockRespond(baseResponse));
  }));

  it('should return response when subscribed to getStatus',
    inject([CoreService], (coreService: CoreService) => {
      coreService.getStatus().subscribe((res: Response) => {
        expect(res.text()).toBe('status');
      });
    })
  );

})

您真正需要注意的是在beforeEachProviders 中进行适当的模拟。测试本身非常简单,最终订阅服务方法。


注意:别忘了先设置base provider:

import { setBaseTestProviders } from '@angular/core/testing';
import {
  TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS,
  TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
} from '@angular/platform-browser-dynamic/testing';

setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);

【讨论】:

    【解决方案2】:

    自从提出这个问题后,我们确实升级到了 Angular2 RC 1。我们的导入看起来像 Wojciech Kwiatek 的(感谢您的回答!)但我们的测试策略略有不同。我们想断言请求以及响应。我们没有使用beforeEachProviders(),而是使用beforeEach() 创建自己的注入器并保存对被测服务和模拟后端的引用。这让我们可以断言请求并在测试中管理响应,并且可以让我们在每次测试后使用verifyNoPendingRequests() 方法。

    describe('core-service', () => {
    
      let service: CoreService;
      let backend: MockBackend;
    
      beforeEach(() => {
        injector = ReflectiveInjector.resolveAndCreate(<any> [
            CoreService,
            BaseRequestOptions,
            MockBackend,
            provide(Http, {
                useFactory: (mockBackend, defaultOptions) => new Http(mockBackend, defaultOptions),
                deps: [MockBackend, BaseRequestOptions]
            })
        ]);
    
        service = <CoreService> injector.get(CoreService);
        backend = <MockBackend> injector.get(MockBackend);
      });
    
      afterEach(() => backend.verifyNoPendingRequests());
    
      it('should get status', () => {
    
        backend.connections.subscribe((c: MockConnection) => {
          expect(c.request.url).toEqual('api/status');
          c.mockRespond(new Response(new ResponseOptions({ body: 'all is well' })));
        });
    
        service.getStatus().subscribe((status) => {
          expect(status).toEqual('all is well');
        });
    
      }));
    });
    

    编辑:Plunker 更新到 RC2。 https://plnkr.co/edit/nlvUZVhKEr8d2mz8KQah?p=preview

    【讨论】:

    • 感谢这个和更新的 Plunker。对我来说,最后一个难题是阅读 ResponseOptions 对象以创建格式良好的模拟响应。一旦我解决了这个问题,我的测试就会使用你的策略。
    猜你喜欢
    • 2017-03-14
    • 1970-01-01
    • 1970-01-01
    • 2014-11-21
    • 1970-01-01
    • 2016-06-23
    • 1970-01-01
    • 1970-01-01
    • 2017-02-16
    相关资源
    最近更新 更多