如何模拟 @ngrx/store?

4

我想对一个组件进行单元测试。我正在使用 @ngrx/store 和 rxjs 来从组件更新我的存储区。为此,我订阅并取消订阅存储区。

我的单元测试失败,并显示以下错误:

TypeError: undefined is not an object (evaluating 'this.store.unsubscribe') in config/spec-bundle.js

我无法找到正确模拟我的存储区及其事件发射器的方法。

以下是相关代码:

app.component.ts:

  ngOnDestroy() {
    // unsubscribe the observable
    this.userStore.unsubscribe();
  }

app.component.spec.ts :

如何模拟存储器:

        export class MockStore extends Subject<fromRoot.State> implements Store <fromRoot.State> {}

如何配置:

如何进行配置:

        TestBed.configureTestingModule({
        imports: [
            FormsModule,
            ReactiveFormsModule,
            StoreModule.provideStore(reducer)
        ],
        declarations: [LoginComponent],
        providers: [
            BaseRequestOptions,
            MockBackend,
            { provide: AuthService, useValue: authServiceStub },
            { provide: LoginService, useValue: loginServiceStub },
            { provide: LoggerService, useValue: loggerServiceStub },
            {
                provide: Http, useFactory: (backend: ConnectionBackend,
                    defaultOptions: BaseRequestOptions) => {
                    return new Http(backend, defaultOptions);
                }, deps: [MockBackend, BaseRequestOptions]
            },
            { provide: Router, useClass: MockRouter }
        ]
  })

告诉我如果你需要更多信息。提前感谢。
2个回答

9

有多种方法可以模拟存储,你的做法将取决于你想要测试的方式。

当我在测试中模拟存储时,我只关心两件事:

  • 能够轻松地发出与测试相关的状态;
  • 能够在测试期间查看分派到存储的任何操作。

我不关心连接reducer,因为它们在其他地方进行了测试,所以这是我的做法。

我使用类似这样的函数从两个Subject实例创建一个Store

import { Action, Store } from "@ngrx/store";
import { Subject } from "rxjs/Subject";

export function mockStore<T>({
  actions = new Subject<Action>(),
  states = new Subject<T>()
}: {
  actions?: Subject<Action>,
  states?: Subject<T>
}): Store<T> {

  let result = states as any;
  result.dispatch = (action: Action) => actions.next(action);
  return result;
}

在每个beforeEach中,我都会创建一个Store并将其添加到TestBedproviders中:

actions = new Subject<Action>();
states = new Subject<AppState>();
store = mockStore<AppState>({ actions, states });

TestBed.configureTestingModule({
  imports: [EffectsTestingModule],
  providers: [
    {
      provide: Store,
      useValue: store
    },
    ...
  ]
});

使用在组件、效果或其他你正在测试的地方注入的模拟的Store,在测试中发出特定状态就很简单了 - 使用states主题 - 并且很容易检查是否已分派任何预期操作 - 通过订阅actions主题。

关于你的错误,你不应该在Store上调用unsubscribe,而应该在订阅存储时返回的Subscription对象上调用它,如另一个答案所述。


谢谢你的回答。我按照你的方法尝试了,但仍然出现错误...正如我在另一个答案中所说,我已经在Subscription对象上调用了取消订阅。 - Megan

1
问题可能出在您的取消订阅上。您应该在您实际的订阅对象上调用unsubscribe方法,而不是在可观察对象/Store上调用。例如,如果您有以下类型的订阅:
this.subscription = this._store
    .select('users')
    .subscribe(users => {
      this.users = users;
  });

you unsubscribe like this:

  ngOnDestroy() {
    // unsubscribe the observable
    this.subscription.unsubscribe();
  }

此外,我不确定你是否遗漏了一些代码,但是你模拟了你的存储方式,很可能只需使用实际的存储方式,然后对分派进行间谍监视即可。

1
谢谢你的回答 :) 由于我的组件和测试非常重要,所以有些代码被省略了。我已经按照你在评论中描述的取消了订阅,所以我想我的错误可能出在其他地方...this.userStore = this.store.let(fromRoot.get).subscribe((state: any) => { // navigates to welcome page if connection }); - Megan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接