如何在组件

时间:2018-03-14 21:37:39

标签: angular jasmine ngrx

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

public isListLoading$ = this.store.select(fromStore.getLoading);
public users$ = this.store.select(fromStore.getUsers);

使用ngrx fromStore.method方法创建createSelector。例如:

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

我在模板中使用这些observable:

<div class="loader" *ngIf="isLoading$ | async"></div>
<ul class="userList">
    <li class="userItem" *ngFor="let user of $users | async">{{user.name}}</li>
</div>

我想写一个测试,我可以做一些像:

store.select.and.returnValue(someSubject)

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

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

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

5 个答案:

答案 0 :(得分:5)

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

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

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

然而,在我这样做之前,我必须在你的问题中解决这个问题。

您的解决方案取决于您保存数据的位置。如果您将其保存在constructor中,请执行以下操作:

constructor(private store: Store) {
  this.users$ = store.select(getUsers);
}

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

const createComponent = (): MyComponent => {
  fixture = TestBed.createComponent(MyComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();
  return component;
};

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

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

或者,如果您在ngOnInit中设置值:

constructor(private store: Store) {}
ngOnInit() {
  this.users$ = this.store.select(getUsers);
}

事情变得容易一些,因为您可以创建一次组件,每次想要更改商店的返回值时只调用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
  });
});

答案 1 :(得分:0)

我也遇到了这个问题,对我来说,使用服务来包装选择器也是不可行的。特别是不仅出于测试目的,而且因为我使用商店来替换服务。

因此,我想出了以下(也不是完美的)解决方案:

我对每个组件和每个不同方面使用不同的“存储”。在您的示例中,我将定义以下Stores&States:

export class UserStore extends Store<UserState> {}

export class LoadingStatusStore extends Store<LoadingStatusState> {}

并将它们注入用户组件中

constructor( private userStore: UserStore, private LoadingStatusStore: 
LoadingStatusStore ) {}

在用户组件测试类中模拟它们:

TestBed.configureTestingModule({
  imports: [...],
  declarations: [...],
  providers: [
    { provide: UserStore, useClass: MockStore },
    { provide: LoadingStatusStore, useClass: MockStore }
  ]
}).compileComponents();

将它们注入到beforeEach()或it()测试方法中:

beforeEach(
  inject(
    [UserStore, LoadingStatusStore],
      (
        userStore: MockStore<UserState>,
        loadingStatusStore: MockStore<LoadingStatusState>
      ) => {...}

然后,您可以使用它们来监视不同的管道方法:

const userPipeSpy = spyOn(userStore, 'pipe').and.returnValue(of(user));
const loadingStatusPipeSpy = spyOn(loadingStatusStore, 'pipe')
  .and.returnValue(of(false));

此方法的缺点是您仍然不能在一种测试方法中测试商店状态的多个部分。但是现在对我来说这是一种解决方法。

答案 2 :(得分:0)

您可以使用类似的东西:

spyOn(store, 'select').and.callFake(selectFake);

function pipeFake(op1: OperatorFunction<UsersState, any>): Observable<any> {
  if (op1.toString() === fromStore.getLoading.toString()) {
    return of(true);
  }

  if (op1.toString() === fromStore.getUsers.toString()) {
    return of(fakeUsers);
  }

  return of({});
}

答案 3 :(得分:0)

我创建了这样的助手:

class MockStore {
        constructor(public selectors: any[]) {
        }

        select(calledSelector) {
          const filteredSelectors = this.selectors.filter(s => s.selector === calledSelector);
          if (filteredSelectors.length < 1) {
            throw new Error('Some selector has not been mocked');
          }
          return cold('a', {a: filteredSelectors[0].value});
        }
 }

现在我的测试如下:

  const mock = new MockStore([
    {
      selector: selectEditMode,
      value: initialState.editMode
    },
    {
      selector: selectLoading,
      value: initialState.isLoading
    }
  ]);

  it('should be initialized', function () {
    const store = jasmine.createSpyObj('store', ['dispatch', 'select']);
    store.select.and.callFake(selector => mock.select(selector));

    const comp = new MyComponent(store);

    comp.ngOnInit();

    expect(comp.editMode$).toBeObservable(cold('a', {a: false}));
    expect(comp.isLoading$).toBeObservable(cold('a', {a: false}));
  });

答案 4 :(得分:0)

如果要测试选择器本身,则将选择器移入服务并不会消除对选择器进行模拟的需要。 ngrx现在具有自己的模拟方式,在此处进行了描述: https://ngrx.io/guide/store/testing