如何在组件中模拟ngrx选择器

Bla*_*axy 12 jasmine ngrx angular

在组件中,我们使用ngrx选择器来检索状态的不同部分.

public isListLoading$ = this.store.select(fromStore.getLoading);
public users$ = this.store.select(fromStore.getUsers);
Run Code Online (Sandbox Code Playgroud)

fromStore.method是使用NGRX创建createSelector方法.例如:

export const getState = createFeatureSelector<UsersState>('users');
export const getLoading = createSelector(
  getState,
  (state: UsersState) => state.loading
);
Run Code Online (Sandbox Code Playgroud)

我在模板中使用这些observable:

<div class="loader" *ngIf="isLoading$ | async"></div>
<ul class="userList">
    <li class="userItem" *ngFor="let user of $users | async">{{user.name}}</li>
</div>
Run Code Online (Sandbox Code Playgroud)

我想写一个测试,我可以做以下事情:

store.select.and.returnValue(someSubject)
Run Code Online (Sandbox Code Playgroud)

能够更改主题值并再次测试组件的模板这些值.

事实上,我们很难找到一种合适的方法来测试它.如何编写我的"andReturn"方法,因为该select方法在我的组件中被调用两次,有两个不同的方法(MemoizedSelector)作为参数?

我们不想使用真正的选择器,所以模拟一个状态,然后使用真正的选择器似乎不是一个合适的单元测试方式(测试不会被隔离,并将使用真实的方法来测试组件行为).

vin*_*nce 6

我遇到了同样的挑战,并通过将选择器包装在服务中而一劳永逸地解决了问题,因此我的组件只是使用服务来获取其数据,而不是直接通过存储。我发现这清理了我的代码,使我的测试与实现无关,并使模拟更加容易:

mockUserService = {
  get users$() { return of(mockUsers); },
  get otherUserRelatedData$() { return of(otherMockData); }
}

TestBed.configureTestingModule({
  providers: [{ provide: UserService, useValue: mockUserService }]
});
Run Code Online (Sandbox Code Playgroud)

但是,在执行此操作之前,我必须先解决您的问题。

解决方案将取决于您将数据保存在何处。如果以constructor类似方式保存它:

constructor(private store: Store) {
  this.users$ = store.select(getUsers);
}
Run Code Online (Sandbox Code Playgroud)

然后,您每次需要更改返回的值时,都需要重新创建测试组件store。为此,请按照以下方式创建函数:

const createComponent = (): MyComponent => {
  fixture = TestBed.createComponent(MyComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();
  return component;
};
Run Code Online (Sandbox Code Playgroud)

然后在更改商店间谍返回的值之后调用:

describe('test', () => {
  it('should get users from the store', () => {
    const users: User[] = [{username: 'BlackHoleGalaxy'}]; 
    store.select.and.returnValue(of(users));
    const cmp = createComponent();
    // proceed with assertions
  });
});
Run Code Online (Sandbox Code Playgroud)

或者,如果要在中设置值ngOnInit

constructor(private store: Store) {}
ngOnInit() {
  this.users$ = this.store.select(getUsers);
}
Run Code Online (Sandbox Code Playgroud)

事情要容易一些,因为您只需创建一次组件,然后ngOnInit每次想从存储中更改返回值时就调用它:

describe('test', () => {
  it('should get users from the store', () => {
    const users: User[] = [{username: 'BlackHoleGalaxy'}]; 
    store.select.and.returnValue(of(users));
    component.ngOnInit();
    // proceed with assertions
  });
});
Run Code Online (Sandbox Code Playgroud)

  • 谢谢。仅出于测试目的而包装服务是没有意义的。如果您的组件中有多个`select`,那么您的第二个解决方案就可以很好地工作。然后,“ store.select.and.returnValue”将变得不可用,因为您的两个选择都将获得相同的值(这可能会导致应用在测试时崩溃,因为异步管道接收并处理了该地方的无效数据) 。 (2认同)

jur*_*jur 6

如果您要测试选择器本身,则将选择器移至服务中并不会消除模拟选择器的需要。ngrx 现在有自己的模拟方式,如下所述: https ://ngrx.io/guide/store/testing

  • 这应该是公认的答案,它工作完美并且来自官方文档 (2认同)