茉莉花间谍没有回归正确的价值

时间:2017-09-21 11:08:32

标签: angular jasmine karma-jasmine spy

在我的Jasmine测试规范中,有时我会监视authState,我的模拟服务mockAngularFireAuth的属性,并返回代表该特定测试下应用状态的不同值。

在一次测试中,这种方法完美无缺,断言是正确的;看到测试:

AuthService
  catastrophically fails

然而,当我在测试中以完全相同的方式监视authState时(例如)......

AuthService
  can’t authenticate anonymously
    AuthService.currentUid
      should return undefined

......断言expect(service.currentUid).toBeUndefined()失败。

currentUid保留最初设置的字符串(" 17WvU2Vj58SnTz8v7EqyYYb0WRc2")。

以下是我的测试规格的精简版(仅包含相关测试规格):

import { async, inject, TestBed } from '@angular/core/testing';

import { AngularFireAuth } from 'angularfire2/auth';
import 'rxjs/add/observable/of';
import { Observable } from 'rxjs/Rx';

import { AuthService } from './auth.service';
import { MockUser} from './mock-user';
import { environment } from '../environments/environment';

// An anonymous user
const authState: MockUser = {
  displayName: null,
  isAnonymous: true,
  uid: '17WvU2Vj58SnTz8v7EqyYYb0WRc2'
};

// Mock AngularFireAuth
const mockAngularFireAuth: any = {
  auth: jasmine.createSpyObj('auth', {
    'signInAnonymously': Promise.resolve(authState)
  }),
  authState: Observable.of(authState)
};

describe('AuthService', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        { provide: AngularFireAuth, useValue: mockAngularFireAuth },
        { provide: AuthService, useClass: AuthService }
      ]
    });
  });

  …

  describe('can’t authenticate anonymously', () => {

    …

    describe('AuthService.currentUid', () => {
      beforeEach(() => {
        // const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');
        //
        // spy.and.returnValue(Observable.of(null));
        //
        mockAngularFireAuth.authState = Observable.of(null);
      });

      it('should return undefined',
        inject([ AuthService ], (service: AuthService) => {
          expect(service.currentUid).toBeUndefined();
        }));
    });
  });

  describe('catastrophically fails', () => {
    beforeEach(() => {
      const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');

      spy.and.returnValue(Observable.throw(new Error('Some catastrophe')));
    });

    describe('AngularFireAuth.authState', () => {
      it('should invoke it’s onError function', () => {
        mockAngularFireAuth.authState.subscribe(null,
          (error: Error) => {
            expect(error).toEqual(new Error('Some catastrophe'));
          });
      });
    });

    describe('AuthService.currentUid', () => {
      beforeEach(() => {
        mockAngularFireAuth.authState = Observable.of(null);
      });

      it('should return undefined',
        inject([ AuthService ], (service: AuthService) => {
          expect(service.currentUid).toBeUndefined();
        }));
    });
  });

  describe('is authenticated anonymously already', () => {
    beforeEach(() => {
      // const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');
      //
      // spy.and.returnValue(Observable.of(authState));
      //
      mockAngularFireAuth.authState = Observable.of(authState);
    });

    describe('.authSate.isAnonymous', () => {
      it('should be true', async(() => {
        mockAngularFireAuth.authState.subscribe((data: MockUser) => {
          expect(data.isAnonymous).toBeTruthy();
        });
      }));
    });

    describe('AuthService.currentUid', () => {
      it('should return "17WvU2Vj58SnTz8v7EqyYYb0WRc2"',
        inject([ AuthService ], (service: AuthService) => {
          expect(service.currentUid).toBe('17WvU2Vj58SnTz8v7EqyYYb0WRc2');
        }));
    });
  });

  describe('is authenticated with Facebook already', () => {
    beforeEach(() => {
      const obj: MockUser = authState;
      // const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');
      //
      // spy.and.returnValue(Observable.of(Object.assign(obj, {
      //   isAnonymous: false,
      //   uid: 'ZzVRkeduEW1bJC6pmcmb9VjyeERt'
      // })));
      //
      mockAngularFireAuth.authState = Observable.of(Object.assign(obj, {
        isAnonymous: false,
        uid: 'ZzVRkeduEW1bJC6pmcmb9VjyeERt'
      }));
    });

    describe('.authSate.isAnonymous', () => {
      it('should be false', () => {
        mockAngularFireAuth.authState.subscribe((data: MockUser) => {
          expect(data.isAnonymous).toBe(false);
        });
      });
    });

    describe('AuthService.currentUid', () => {
      it('should return "ZzVRkeduEW1bJC6pmcmb9VjyeERt"',
        inject([ AuthService ], (service: AuthService) => {
          expect(service.currentUid).toBe('ZzVRkeduEW1bJC6pmcmb9VjyeERt');
        }));
    });
  });
});

您可以看到我在哪里评论了间谍,而不得不劫持authSate mockAngularFireAuth属性,以使断言成功,强行改变它的价值 - 我不应该做的事情mockAngularFireAuth是一个常数。

为了完整性,这是(部分)被测服务:

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

import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import 'rxjs/add/observable/of';
// import 'rxjs/add/operator/catch';
import { Observable } from 'rxjs/Rx';

@Injectable()
export class AuthService {
  private authState: firebase.User;

  constructor(private afAuth: AngularFireAuth) { this.init(); }

  private init (): void {
    this.afAuth.authState.subscribe((authState: firebase.User) => {
      if (authState === null) {
        this.afAuth.auth.signInAnonymously()
          .then((authState: firebase.User) => {
            this.authState = authState;
          })
          .catch((error: Error) => {
            console.error(error);
          });
      } else {
        this.authState = authState;
      }
    }, (error: Error) => {
      console.error(error);
    });
  }

  public get currentUid(): string {
    return this.authState ? this.authState.uid : undefined;
  }
}

是因为断言失败的规范我没有订阅authState,因此间谍没有返回我设置的相应值吗?

更新

我认为这可能是因为Jasmine无法监视不属于函数的属性(不是我的知识),或者是getter / setter。

但是为什么间谍在

AuthService
  catastrophically fails

通过?

1 个答案:

答案 0 :(得分:0)

根据我的更新;你不能监视属性 - 只有函数(或方法)和属性getter和setter。

相反,我向setAuthState添加了一个mockAngularFireAuth方法,如果authState属性可以改变该值。

它基本上与我完全一样,但没有违反TypeScript中的常量规则。由于它是一个模拟服务,我认为这个附加方法不存在重要。

然而,我并不完全确定成功规范的原因。我想这可能是因为方法throw就是这样一个功能;因此它可能成为茉莉花间谍的回归价值。

以下是我改变测试的方式:

// Mock AngularFireAuth
const mockAngularFireAuth: any = {
  auth: jasmine.createSpyObj('auth', {
    'signInAnonymously': Promise.resolve(authState)
  }),
  authState: Observable.of(authState),
  setAuthState: (authState: MockUser): void => {
    mockAngularFireAuth.authState = Observable.of(authState);
  }
};

请注意setAuthState

这就是我改变规格的方式(代表性的例子):

describe('AuthService.currentUid', () => {
  beforeEach(() => {
    mockAngularFireAuth.setAuthState(null);
  });

  it('should return undefined',
    inject([ AuthService ], (service: AuthService) => {
      expect(service.currentUid).toBeUndefined();
    }));
});