尖刺的服务不符合预期

wil*_*bur 5 jasmine typescript angular

在乡亲中,这是一个繁重的代码。我试图将其最小化,但没有一些具体的例子,我看不出如何诊断这一点。

我在我的角度应用程序中为用户服务编写了以下模拟工厂。我们使用karma测试器运行器和jasmine测试框架。

万一重要的是,这里是我们karma和茉莉花测试的所有依赖项

"karma": "4.1.0",
"karma-chrome-launcher": "2.2.0",
"karma-coverage-istanbul-reporter": "2.0.3",
"karma-jasmine": "1.1.0",
"karma-jasmine-html-reporter": "1.3.1",
"karma-trx-reporter": "0.3.0",

"@types/jasmine": "2.8.3",
"@types/jasminewd2": "2.0.2",
"jasmine-core": "3.4.0",
"jasmine-spec-reporter": "4.2.1",
Run Code Online (Sandbox Code Playgroud)

模拟工厂

import {of, throwError} from 'rxjs';
import {CurrentUserModel} from '../models/api-data-models/current-user-model';

export class UserServiceMockFactory {
  public static createUserServiceMock(jasmineSpyOn: any, jasmineSpyOnProperty: any, initialCurrentUserValue?: CurrentUserModel) {

    const currentUserStub = {
      // "as any" cast is essential here.
      // It allows us to set valuePipedThroughCurrentUserGetter to values that are not of type CurrentUserModel.
      // Most common use case would be an error
      valuePipedThroughCurrentUserGetter: !!initialCurrentUserValue ? initialCurrentUserValue as any : null,
      currentUserGetterThrows: false,

      get currentUser() {
        return !this.currentUserGetterThrows
          ? of(this.valuePipedThroughCurrentUserGetter)
          : throwError(this.valuePipedThroughCurrentUserGetter);
      },

      currentUserGetSpy: null, // spy field to assert calls upon currentUser getter

      flushCurrentUser() {
      },

      getUserBySearchTerm(term) {
      }
    };

    currentUserStub.currentUserGetSpy = jasmineSpyOnProperty(currentUserStub, 'currentUser', 'get').and.callThrough();

    jasmineSpyOn(currentUserStub, 'flushCurrentUser');
    jasmineSpyOn(currentUserStub, 'getUserBySearchTerm');

    return currentUserStub;
  }
}
Run Code Online (Sandbox Code Playgroud)

其目的是为用户服务提供一个很好的预模拟实例,其中包含用户可能想要断言的所有间谍,以及通过设置来通过其他用户对象进行管道传输的选项valuePipedThroughCurrentUserGetter

我已经编写了以下测试,并且测试取得了预期的成功。

it('should return new value if valuePipedThroughCurrentUserGetter changed, non-null initialization', () => {
  const expectedCurrentUser = new CurrentUserModel({
    id: '123',
    login: 'jdoe',
    isMaintenanceAndOperations: true,
    isExcluder: true,
    isSupport: true,
    isImpersonator: true,
    isViewAll: true,
    initiators: [],
    name: 'John Doe'
  });

  const userService = UserServiceMockFactory.createUserServiceMock(spyOn, spyOnProperty, new CurrentUserModel({
    id: '123_a',
    login: 'jdoe_a',
    isMaintenanceAndOperations: false,
    isExcluder: false,
    isSupport: false,
    isImpersonator: false,
    isViewAll: false,
    initiators: [],
    name: 'John Doe_a'
  }));
  userService.valuePipedThroughCurrentUserGetter = expectedCurrentUser;
  userService.currentUser
    .subscribe(cu => expect(cu).toEqual(expectedCurrentUser),
      () => fail('should not reach here'));
});
Run Code Online (Sandbox Code Playgroud)

但是,到了在实际测试中使用它的时候……它就崩溃了。我编写了以下最少的代码来隔离故障

import {TestBed} from '@angular/core/testing';
import {UserService} from '../http/user.service';
import {CurrentUserModel} from '../models/api-data-models/current-user-model';
import {UserServiceMockFactory} from './user-service-mock-factory';

describe('When Injected, Mock Behavior Seems to Fail', () => {

  let userService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [{
        provide: UserService,
        useValue: UserServiceMockFactory.createUserServiceMock(spyOn, spyOnProperty,
          new CurrentUserModel({
            id: '123',
            login: 'jdoe',
            isMaintenanceAndOperations: true,
            isExcluder: true,
            isSupport: true,
            isImpersonator: true,
            isViewAll: true,
            initiators: [],
            name: 'John Doe'
          }))
      }]
    });

    userService = TestBed.get(UserService);
  });

  it('should succeed but fails :(', () => {
    const expectedUser = new CurrentUserModel({
      id: '123_a',
      login: 'jdoe_a',
      isMaintenanceAndOperations: false,
      isExcluder: false,
      isSupport: false,
      isImpersonator: false,
      isViewAll: false,
      initiators: [], 
      name: 'John Doe_a'
    });

    userService.valuePipedThroughCurrentUserGetter = expectedUser;

    userService.currentUser.subscribe(cu => {
      expect(cu).toEqual(expectedUser);
    });
  });

});
Run Code Online (Sandbox Code Playgroud)

它失败,并显示以下消息

Expected $.id = '123' to equal '123_a'.
Expected $.login = 'jdoe' to equal 'jdoe_a'.
Expected $.isMaintenanceAndOperations = true to equal false.
Expected $.isExcluder = true to equal false.
Expected $.isSupport = true to equal false.
Expected $.isImpersonator = true to equal false.
Expected $.isViewAll = true to equal false.
Expected $.name = 'John Doe' to equal 'John Doe_a'.
Run Code Online (Sandbox Code Playgroud)

看起来,每当使用模拟服务来代替真实的服务currentUser时,即使它的支持字段很明显地改变了,它也无法更改从返回的值。

我发现奇怪的是,我可以通过修改UserServiceMockFactory使其稍有不同来使其表现出预期的效果。现在,我实现了似乎是同一个类的方法,而不是创建一个通用对象,然后对其进行监视,而是重新创建了它的一个实例,对该实例进行监视,然后瞧瞧!可以正常工作。

我的主要问题是:

为什么在替换实际服务时原始代码的行为会有所不同?

为什么在取代实际服务时新版本能按预期工作?

模拟工厂

import {of, throwError} from 'rxjs';
import {CurrentUserModel} from '../models/api-data-models/current-user-model';

class CurrentUserStub {
  valuePipedThroughCurrentUserGetter: any;
  currentUserGetterThrows = false;

  constructor(init?) {
    this.valuePipedThroughCurrentUserGetter = !!init ? init as any : null;
  }

  get currentUser() {
    return !this.currentUserGetterThrows
      ? of(this.valuePipedThroughCurrentUserGetter)
      : throwError(this.valuePipedThroughCurrentUserGetter);
  }

  currentUserGetSpy = null;

  // spy field to assert calls upon currentUser getter

  flushCurrentUser() {
  }

  getUserBySearchTerm(term) {
  }
}

export class UserServiceMockFactory {
  public static createUserServiceMock(jasmineSpyOn: any, jasmineSpyOnProperty: any, initialCurrentUserValue?: CurrentUserModel) {

    const currentUserStub = new CurrentUserStub(initialCurrentUserValue);

    currentUserStub.currentUserGetSpy = jasmineSpyOnProperty(currentUserStub, 'currentUser', 'get').and.callThrough();

    jasmineSpyOn(currentUserStub, 'flushCurrentUser');
    jasmineSpyOn(currentUserStub, 'getUserBySearchTerm');

    return currentUserStub;
  }
}
Run Code Online (Sandbox Code Playgroud)