在单元测试中:如何覆盖由 entityAdapter.getSelectors() 创建的 NGRX 选择器

Posted

技术标签:

【中文标题】在单元测试中:如何覆盖由 entityAdapter.getSelectors() 创建的 NGRX 选择器【英文标题】:In unit test: How to override an NGRX selector which is created by entityAdapter.getSelectors() 【发布时间】:2021-11-29 18:41:48 【问题描述】:

假设我们的应用有一个书籍页面。 我们正在使用:Angular、NGRX、jest。

提供上下文的一些代码行(见下面的实际问题):

图书页面状态接口:

export interface Book 
  bookID: string;
  whatever: string;


export interface Books extends EntityState<Book> 
  error: boolean;
  loaded: boolean;


export interface BooksFeature extends RootState 
  books: Books;
  //...

作为功能添加到ngrx商店:

@NgModule(
  imports: [
    StoreModule.forFeature('books', booksReducer),
    //...
  ]

ngrx entityAdapter 已创建:

export const booksAdapter = createEntityAdapter<Book>(
  selectId: (book: Book): string => book.bookID,
);

booksAdapterselectAll创建booksSelector

const  selectAll, selectTotal  = booksAdapter.getSelectors((state: BooksFeature) => state.books);

export const booksSelector = selectAll;

booksSelector 分配给组件属性:books$

public books$ = this.store.pipe(select(booksSelector));

然后,books$ 可观察对象用于许多事情(例如,&lt;div *ngIf="(books$ | async).length"&gt; 等...)。

目标:假设如果books$ observable 始终与booksSelector 广播的值相同,我想单独进行单元测试。 p>

通常我会在组件的books.component.spec.ts 文件中执行以下操作:

组件测试的一般设置:

//...
describe('BooksComponent', () => 
  let spectator: Spectator<BooksComponent>
  let store: MockStore;
  const initialState: RootState = 
    //...
    books: 
      ids: [],
      entities: ,
      error: false,
      loaded: false
    
  ;

  const testBook: Book = 
    bookID: 'bookid_1',
    whatever: 'whatever'
  ;

  const createComponent = createComponentFactory(
    component: BooksComponent,
    providers: [
      provideMockStore( initialState )
    ],
    imports: [
      StoreModule.forRoot(),
      detectChanges: false
    ]
  );

  beforeEach(() => 
    spectator = createComponent();
    store = spectator.inject(MockStore);
  );

  afterEach(() => 
    jest.clearAllMocks();
  );
//...

还有重要的部分:

//...
  describe('books$', () => 
      it('books$ should have the same value as what booksSelector gives', () => 
        const testBooks: Book[] = [testBook];
        const expected = cold('a',  a: testBooks );

        const mockBooksSelector = store.overrideSelector(booksSelector, testBooks);

        expect(spectator.component.books$).toBeObservable(expected);
      );
      //... Then I would like to use the mockBooksSelector.setResult(...) method too for other test cases
  );
//...

问题在于MockStoreoverrideSelector 方法需要Selector 作为第一个参数,但entityAdaptergetSelectors 方法返回一个selectAll 方法有不同的类型。

请告诉我如何用适当的解决方案替换此测试!

请记住,问题已被简化以保持重点,我不是在寻找这样的解决方案:

如果使用正确的选择调用 store.pipe,则改为测试。 手动更改状态以获得booksSelector 给出的期望值。 不仅在 .spec 文件中改变事物的解决方案。 (我的意思是,如果这真的是不可避免的,那么好吧)

谢谢!

【问题讨论】:

【参考方案1】:

你需要使用特征选择器作为getSelectors的输入:

const selectBookFeatureState =
  createFeatureSelector<BooksFeature>('books');

const  selectAll, selectTotal  = booksAdapter.getSelectors(selectBookFeatureState)

如果这不起作用,您可以尝试创建这样的选择器:

const selectAllBooks = createSelector(
  selectBookFeatureState, 
  selectAll
)

const selectTotalBooks = createSelector(
  selectBookFeatureState, 
  selectTotal
)

并使用selectAllBooks 代替selectAllselectTotalBooks 代替selectTotal

【讨论】:

以上是关于在单元测试中:如何覆盖由 entityAdapter.getSelectors() 创建的 NGRX 选择器的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Django 中使用不同的设置进行单元测试?

为什么?在单元测试覆盖范围内显示的类即使未在测试目标中添加

如何在 IAR 中执行单元测试和代码覆盖

如何在 Quarkus 中覆盖一个单元测试的配置属性

SonarQube 没有获得单元测试覆盖率

单元测试覆盖在 Sonarqube 中不可见