【问题标题】:Jasmine Spy not Returning Correct Value茉莉花间谍没有返回正确的值
【发布时间】:2018-03-02 17:08:00
【问题描述】:

在我的 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');
        }));
    });
  });
});

你可以看到我在哪里注释掉了间谍,而是不得不劫持 mockAngularFireAuthauthSate 属性以使断言成功,通过强行改变它的价值——一些东西我不应该这样做,因为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

通过?

【问题讨论】:

    标签: angular jasmine karma-jasmine spy


    【解决方案1】:

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

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

    它本质上和我做的完全一样,但没有破坏 TypeScript 中的常量规则。因为它是一个模拟服务,所以我认为这个附加方法的存在并不重要。

    但是,我不完全确定成功的规范为何如此。我想可能是因为throw方法就是这样一个函数;因此它可能成为 Jasmine spy 的返回值。

    这是我更改测试的方式:

    // 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();
        }));
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-28
      • 1970-01-01
      • 1970-01-01
      • 2014-01-25
      • 2012-08-15
      • 2021-12-12
      相关资源
      最近更新 更多