如何在 Angular 中模拟 AWS Amplify 库?

How to mock AWS Amplify library in Angular?

当我 运行 使用 Karma 进行测试套件时,我收到一个似乎来自 AWS Amplify 的错误。

AuthEffects
    login
      √ should not dispatch any action
      √ should call setItem on LocalStorageService
Chrome 78.0.3904 (Windows 10.0.0) ERROR
  An error was thrown in afterAll
  Uncaught TypeError: Cannot read property 'clientMetadata' of undefined thrown

据我推测,这个错误是从上次启动的测试中抛出的:AuthEffects

在我的 AuthEffects 中,我必须这样做才能使 AWS amplify 正常工作

import { Auth } from 'aws-amplify';
//...

const promise = Auth.signIn(username, password);

我不明白如何模拟 API 对 Cognito 的访问。 通常,我通过依赖注入为构造函数提供 Mock 服务,以避免真正连接到 API。这里是直接在组件中导入的。

规格文件:

describe('login', () => {
    it('should not dispatch any action', () => {
      const actions = new Actions(EMPTY);
      const effect = new AuthEffects(
      //...
      );
      const metadata = getEffectsMetadata(effect);

      expect(metadata.login).toEqual({ dispatch: false });
    });

    it('should call setItem on LocalStorageService', () => {
      const loginAction = new ActionAuthLogin('test', 'Test1234!');
      const source = cold('a', { a: loginAction });
      const actions = new Actions(source);
      const effect = new AuthEffects(
      //...
      );

      effect.login.subscribe(() => {
        expect(localStorageService.setItem).toHaveBeenCalledWith(AUTH_KEY, {
          isAuthenticated: true
        });
      });
    });

    afterAll(() => {
      TestBed.resetTestingModule();
    });
  });

有没有办法从规范文件中覆盖此导入?

我设法摆脱了错误。

您需要在测试文件中导入 Auth 以及以下解决方案中需要的类型。

import { Auth } from 'aws-amplify';
import { ClientMetaData, SignInOpts } from '@aws-amplify/auth/src/types/Auth'; 

现在重新定义Auth的signIn方法即可解决问题:

describe('AuthEffects', () => {
//...
Auth.signIn = (
    usernameOrSignInOpts: string | SignInOpts,
    pw?: string,
    clientMetadata: ClientMetaData = this._config.clientMetadata
  ) => of({}).toPromise();
//...

您需要按照方法的签名来覆盖它。

public signIn(
        usernameOrSignInOpts: string | SignInOpts,
        pw?: string,
        clientMetadata: ClientMetaData = this._config.clientMetadata
    ): Promise<CognitoUser | any> 

我不知道这是否是执行它的最佳(更清洁)解决方案,但我设法消除了错误并控制了 Auth 实例的行为。

与其将其直接导入您的服务或效果 class,不如将其导入您的模块并注入。假设您有一个 AuthService 服务 class 处理 AWS Amplify 的 Auth。

在你对应的模块中:

import Auth, { AuthClass } from '@aws-amplify/auth';
...
providers: [
    AuthService,
    { provide: AuthClass, useValue: Auth }
  ]

在您的组件或服务中,像任何其他依赖项一样简单地注入它:

constructor(
    private auth: AuthClass
    ) {
       auth.signIn('abc', 'def')
    }

最后,在您的规范文件中模拟它并使用间谍:

describe('AuthService', () => {
  let service: AuthService;
  let authSpy: jasmine.SpyObj<AuthClass>
  let authSpyObj = {
    signIn: () => new Promise<any>(() => true),
    signOut: () => new Promise<any>(() => true),
    signUp: () => new Promise<any>(() => true),
    confirmSignUp: () => new Promise<any>(() => true),
    resendSignUp: () => new Promise<any>(() => true),
  }

  beforeEach(() => {
    const spyAuth = jasmine.createSpyObj('auth', authSpyObj);

    TestBed.configureTestingModule({
      providers: [
        AuthService,
        { provide: AuthClass, useValue: spyAuth },
      ]
    })

    service = TestBed.inject(AuthService)
    authSpy = TestBed.inject(AuthClass) as jasmine.SpyObj<AuthClass>    
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should have called signIn', () => {
    service.signIn('a@b.c', '12345678')
    expect(authSpy.signIn).toHaveBeenCalled()
  })
});

除了确认您的 service/component 正在调用 expect Auth 函数外,该测试文件现在没有做太多事情,但这应该让您离开地面。享受吧。

你可以这样做:

// service with Auth
import { Injectable } from '@angular/core';
import Auth, { CognitoUser } from '@aws-amplify/auth';
...

private getProfile(): void {
   return from(Auth.currentUserInfo());
}


// service.spec.ts
it('should set user and user profile', async () => {
   const userProfile = { profile: 'userProfile' } as any;
    
   Auth.currentUserInfo = jasmine.createSpy().and.callFake(() => Promise.resolve(userProfile));
       
   const resp = await service.getProfile.toPromise();
   expect(resp).toEqual(userProfile)
      
 });