Как имитировать магазин с начальным состоянием функционального модуля в родительском модуле приложения

Я использую angular 8. У меня есть модуль app и подмодуль «feature». У обоих есть свои магазины. app.module.ts имеет

@NgModule({
 imports: [
    ...
    StoreModule.forRoot({ 'app': reducer }),
    ...
}]

Таким же образом, в модуле 'feature' есть

StoreModule.forFeature('feature', fromFeature.reducer)

Теперь, тестируя компонент уровня app в jasmine, я издеваюсь над Магазином, как показано ниже.

let store: MockStore<State>;
 TestBed.configureTestingModule({
 imports: [
   StoreModule.forRoot({'app' : reducer})
 ],
 providers: [
   provideMockStore({initialState})
 ]

где initialState - начальное состояние модуля app. Однако при попытке получить store = TestBed.get(Store); я получаю ошибку для неопределенных значений для state модуля feature, когда он пытается получить доступ к селекторам из модуля feature.

Я понимаю, что мне нужно инициализировать mockStore, указав состояние всего приложения, а не только app состояние модуля. Однако я не знаю, как этого добиться.

Я попытался импортировать StoreModule.forFeature('feature', featureReducer) и инициализировать mockstore как provideMockStore({initialState:: {initialState, featureInitialState}}), но все равно получаю состояние функции undefined для селекторов.

Ошибка выглядит следующим образом

ERROR TypeError: Cannot read property 'isEntity' of undefined
at getIsEntity (VM17 main.js:50252)
at store.js:583
at memoized (store.js:525)
at defaultStateFn (store.js:552)
at store.js:586
at memoized (store.js:525)
at MapSubscriber.project (store.js:480)

где getIsEntity - это feature селектор модуля, определяемый следующим образом:

const getIsEntity = (state: State): boolean => state.isEntity

Как я могу имитировать / передать начальное состояние для функционального модуля в родительском модуле приложения?


person kaur    schedule 03.02.2021    source источник


Ответы (2)


Смоделируйте его как корневое хранилище, потому что нет собственных хранилищ, все редукторы / действия и эффекты всегда работают с хранилищем (одно корневое хранилище), тогда как вызов forFeature создает впечатление, что у них есть собственные хранилища.

Нет, они обращаются к одному и тому же корневому хранилищу.

let store: MockStore<State>;
 TestBed.configureTestingModule({
 imports: [
   StoreModule.forRoot({
     'app' : reducer,
     'feature': ....,
   })
 ],
 providers: [
   provideMockStore({initialState})
 ]

К сожалению, из-за небольшого количества информации сложно указать точное место ошибки, но ниже вы можете проверить рабочий пример:

describe('suite', () => {
    let fixture: MockedComponentFixture<TestedComponent>;
    let storeState: DeepPartial<StoreState & {ngrxCorrelationId: {tasks: Array<string>; payloads: unknown}}>;

    beforeEach(() => {
        const actions = new Observable<{}>();
        storeState = {
            ngrxCorrelationId: {
                tasks: [],
                payloads: {},
            },
            v2ProjectEntity: {
                ids: [],
                entities: {},
                lists: {},
            },
            v2PermissionsEntity: {},
        };

        TestBed.configureTestingModule({
            declarations: [TestedComponent],
            providers: [
                provideMockStore({
                    initialState: storeState,
                }),
                provideMockActions(() => actions),
                {
                    provide: LoaderService,
                    useValue: createSpyObj('LoaderService', ['setLoaderStatus', 'hideLoader']),
                },
                {
                    provide: Router,
                    useValue: createSpyObj('Router', ['navigate', 'navigateByUrl']),
                },
                {
                    provide: ActivatedRoute,
                    useValue: {
                        queryParams: new Subject(),
                    },
                },
                TestedEffects,
            ],
        });
        fixture = MockRender(TestedComponent);
    });

    it('listens on initial load public projects of the smart component', inject(
        [ActivatedRoute, Store],
        (activatedRoute: ActivatedRoute, store: Store<{}>) => {
            const props = {
                cid: 'public',
                options: {
                    relations: [
                        'location',
                        'location.address',
                        'quotations',
                        'quotations.owner',
                        'quotations.owner.company',
                        'quotations.documents',
                        'certificates',
                        'certificatesCustom',
                        'documents',
                    ],
                    scopes: ['public'],
                    filters: {
                        category: undefined,
                        deadline: undefined,
                    },
                    order: undefined,
                },
            };
            spyOn(store, 'dispatch');
            (<Subject<{}>>activatedRoute.queryParams).next({});
            fixture.detectChanges();

            expect(store.dispatch).toHaveBeenCalledTimes(1);
            expect(store.dispatch).toHaveBeenCalledWith(publicProjectsListLoad({...props}));
        },
    ));

    it('listens on load public projects with filters of the smart component', inject(
        [ActivatedRoute, Store],
        (activatedRoute: ActivatedRoute, store: Store<{}>) => {
            const props = {
                cid: 'public',
                options: {
                    relations: [
                        'location',
                        'location.address',
                        'quotations',
                        'quotations.owner',
                        'quotations.owner.company',
                        'quotations.documents',
                        'certificates',
                        'certificatesCustom',
                        'documents',
                    ],
                    scopes: ['public'],
                    filters: {
                        category: ['transformer'],
                        deadline: ['0-10'],
                    },
                    order: ['projectName:desc'],
                },
            };
            spyOn(store, 'dispatch');
            (<Subject<{}>>activatedRoute.queryParams).next({
                'filters[category]': 'transformer',
                'filters[deadline]': '0-10',
                order: 'projectName:desc',
            });
            fixture.detectChanges();

            expect(store.dispatch).toHaveBeenCalledTimes(1);
            expect(store.dispatch).toHaveBeenCalledWith(publicProjectsListLoad({...props}));
        },
    ));
});
person satanTime    schedule 03.02.2021
comment
Я попытался выполнить импорт как root, но все равно получаю те же предупреждения. initialState в provideMockStore - это только начальное состояние хранилища app? Я пробовал комбинировать с featureInitialState, все те же предупреждения - person kaur; 03.02.2021
comment
Я добавил пример. Возможно, это может быть полезно. - person satanTime; 04.02.2021
comment
Что я вижу в своих тестах для магазина, я вообще не импортирую StoreModule.forRoot, возможно, это и есть причина проблемы. - person satanTime; 04.02.2021
comment
Спасибо, SatanTime. Я наконец нашел проблему. У меня были отдельные состояния для модулей, и состояние приложения не содержало их всех. Итак, когда я издевался только над состоянием приложения, тест также нуждался в фиктивном состоянии модуля, которого не существовало в исходном состоянии, которое я переходил в фиктивное хранилище. - person kaur; 11.02.2021

Я наконец нашел проблему. У меня были отдельные состояния для модулей, и состояние приложения не содержало их всех. Итак, когда я издевался только над состоянием приложения, тест также нуждался в фиктивном состоянии модуля, которого не существовало в исходном состоянии, которое я передавал в фиктивное хранилище.

В соответствии с документами Angular 8, как показали примеры, состояние магазина приложений должно иметь состояние всех модулей, чего в моем случае не хватало.

Исправили это с помощью пластыря, в котором мы создали поддельное начальное состояние, расширяющее состояния приложений и модулей. А затем передать это фальшивое начальное состояние в фиктивный магазин.

person kaur    schedule 11.02.2021