如何为调用服务的函数编写单元 and/or e2e 测试

How to write an unit and/or e2e test for function that calls services

我将如何为这个函数编写单元测试?提交表单时,它会发送 POST 请求以获取登录令牌。通过令牌响应,它会发送另一个请求以获取特定的用户详细信息。

函数如下:

// Log the user in and save the token, then get the details
onLoginSubmit() {
    this.loggingIn = true;
    // request the token then the details for the user based off the token
    this.userService
        .LoginUser(this.loginForm.value.username, this.loginForm.value.password)
        .pipe(
            tap(res => {
                // console.log('Obtained Token:', res);
                localStorage.setItem('login_token', res.token);
                // this.utilityService.SetLocalStorage('login_token', res.token, 1, 'd');
            }),
            concatMap(() => this.userService.GetTokenDetails()),
        )
        .subscribe(
            res => {
                // console.log('Obtained User Data:', res);
                localStorage.setItem('user', res.user);
                // this.utilityService.SetLocalStorage('user', res.user, 1, 'd');
                this.NavigateToRoute();
            },
            () => {
                this.loggingIn = false;
            },
        );
}

服务功能如下:

// logs in the user
LoginUser(email, password): Observable<any> {
    return this.utilityservice.post('universal/login', { email, password }, this.utilityservice.GetHeaders());
}

// gets details of the current logged in user
GetTokenDetails() {
    return this.utilityservice.get('universal/userAPI', { whoami: '' });
}

对于单元测试,我假设我需要创建一个模拟服务,其中这些函数将 return 如下所示的有效响应:

// logs in the user
LoginUser(email, password): Observable<any> {
    return { res: { token: 'test' } };
}

这是正确的方法还是我完全错了?此外,当涉及到我的登录页面的 E2E 测试时,我基本上是通过编写模拟单击表单按钮和输入值的代码来模拟每个测试中的用户,然后检查以确保我得到有效的响应或者我是否过于复杂?

大多数测试库,如 Jasmine 或 Mocha,将包含模拟工具来帮助您编写这些测试;你在其他方面是对的。

您的单元测试不应测试服务,而应仅测试被测代码,在本例中它似乎是一个组件。

我个人在我的规范中使用 RxJs 库 - 所以我可以像这样创建一个模拟 class,并提供一个实例:

class MockUserService {
  public loginSubject = new Subject();
  public loginCalls = [];
  // logs in the user
  public LoginUser(email, password): Observable<any> {
    this.loginCalls.push({ email, password });
    return this.loginSubject.asObservable();
  }
}

所以在我的规范的其他地方我可以做这样的事情:

it('calls login on the user service', () => {
  expect(mockUserService.loginCalls.length > 0).toBe(true);
});

it('sets value on local storage', fakeAsync(() => {
  mockUserService.loginSubject.next({ res: { token: 'test-token' }});
  flushMicrotasks();
  expect(mockLocalStorage.setItem).toHaveBeenCalledWith('login_token', 'test-token', 1, 'd');
}));

规范通常使用 Jasmine,因此您实际上可以像这样设置它(此处的示例规范使用 jasmine 的结构)。

loginSubject = new Subject();
const mockUserService = jasmine.createSpyObj('UserService', ['LoginUser']);
mockUserService.LoginUser.and.returnValue(loginSubject.asObservable());

要使其正常工作,还需要进行更多设置;依赖注入很棒,因为它使您能够编写规范,但它也添加了一些样板。我通常像这样在顶部配置我的测试:

describe('LoginComponent', () => {
  let loginSubject: Subject<any>;
  let mockLocalStorage: jasmine.SpyObj<LocalStorageService>;
  let fixture: ComponentInstance<LoginComponent>;

  beforeEach(() => {
    loginSubject = new Subject();
    const mockUserService = jasmine.createSpyObj('UserService', ['LoginUser']);
    mockUserService.LoginUser.and.returnValue(loginSubject.asObservable());

    mockLocalStorage = jasmine.createSpyObj('LocalStorage', ['setItem']);

    TestBed.configureTestingModule({
      declarations: [LoginComponent],
      providers: [
        { provide: UserService, useValue: mockUserService },
        { provide: LocalStorageService, useValue: mockLocalStorage }
      ]
    });

    fixture = TestBed.createComponent(LoginComponent);
  });

  it('calls login on the user service', () => {
    expect(mockUserService.loginCalls.length > 0).toBe(true);
  });

  it('sets value on local storage', fakeAsync(() => {
    loginSubject.next({ res: { token: 'test-token' }});
    flushMicrotasks();
    expect(mockLocalStorage.setItem).toHaveBeenCalledWith('login_token', 'test-token', 1, 'd');
  }));
});

我做了很多假设(比如你已经以某种方式内联了你的模板,否则你需要在创建组件时异步你的 beforeEach)。

最后,你应该看看这里的手册:https://angular.io/guide/testing

您问了 2 个问题;关于端到端测试。假设您必须登录,登录页面会针对所有其他规范进行测试。我的配置是使用我的注册页面创建一个新用户,注销然后使用这些凭据登录。之后,我继续进行其他测试。这就是我避免为测试设置大量固定数据的方法。

真的,对于 e2e 来说重要的是根据应用程序的需求而变化。我会选择一些您最重要的页面,并确保它们被 e2e 覆盖,因为 e2e 规范 1.) 构建成本高,2.) 可能不稳定。但是,登录页面几乎总是 "what is most important for an app" 的一部分。