'Angular with Jest Unit Test: Cannot read properties of undefined (reading 'pipe')

I want to unit test an Angular component with a NgRx store.

This is my component:

import { Component, OnInit } from '@angular/core';
import { FormGroup, Validators } from '@angular/forms';
import { select } from '@ngrx/store';
import { catchError, Observable, of } from 'rxjs';
import { View } from 'src/app/core/models/view.model';
import { selectIsAuthenticated } from 'src/app/core/store/selectors/auth.selectors';
import { BaseAuthComponent } from '../base-auth/base-auth.component';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss'],
})
export class LoginComponent extends BaseAuthComponent implements OnInit {
  isAuthenticated$: Observable<boolean>;

  loginForm: FormGroup;
  formSubmitted = false;
  loginError = false;

  ngOnInit(): void {
    this.isAuthenticated$ = this.store.pipe(select(selectIsAuthenticated));

    this.loginForm = this.formBuilder.group({
      email: ['[email protected]', [Validators.required, Validators.email]],
      password: ['wfojewfioerjferergER$§T', [Validators.required]],
      rememberMe: [false],
    });
  }

  login(): void {
    this.formSubmitted = true;

    if(this.loginForm.valid) {
      this.authService.login(this.loginForm.value).pipe(
        catchError(() => {
          this.formSubmitted = false;
          this.loginError = true;

          return of(false);
        })
      )
      .subscribe(data => {
        data ? this.onSuccess.emit() : {};
      });
    }
  }

  changeView(): void {
    this.onViewChange.emit(View.REGISTER);
  }
}

My test file looks like this:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideMockStore } from '@ngrx/store/testing';
import { selectIsAuthenticated } from 'src/app/core/store/selectors/auth.selectors';
import { LoginComponent } from './login.component';
const spyOn = jest.spyOn;

describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;

  const initialState = {
    auth: {
      authenticated: false
    }
  };

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [],
      declarations: [ LoginComponent ],
      providers: [
        provideMockStore({
          initialState,
          selectors: [
            { selector: selectIsAuthenticated, value: true }
          ]
        })
      ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

The login component extends from this component:

import { Component, EventEmitter, Output } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Store } from '@ngrx/store';
import { View } from 'src/app/core/models/view.model';
import { AuthService } from 'src/app/core/services/auth/auth.service';
import { FormService } from 'src/app/core/services/form.service';
import { AppState } from 'src/app/core/store/reducers';

@Component({
  template: ''
})
export abstract class BaseAuthComponent {
  @Output() onSuccess = new EventEmitter();
  @Output() onViewChange = new EventEmitter<View>();

  constructor(
    protected formBuilder: FormBuilder,
    protected authService: AuthService,
    protected store: Store<AppState>,
    public formService: FormService
  ) { }
}

If I try to run this test I get the following error:

 FAIL  src/app/modules/auth/login/login.component.spec.ts
  LoginComponent
    × should create (187 ms)                                                                                                  
                                                                                                                              
  ● LoginComponent › should create                                                                                            
                                                                                                                              
    TypeError: Cannot read properties of undefined (reading 'pipe')

      20 |
      21 |   ngOnInit(): void {
    > 22 |     this.isAuthenticated$ = this.store.pipe(select(selectIsAuthenticated));
         |                                        ^
      23 |
      24 |     this.loginForm = this.formBuilder.group({
      25 |       email: ['[email protected]', [Validators.required, Validators.email]],

      at LoginComponent.ngOnInit (src/app/modules/auth/login/login.component.ts:22:40)
      at callHook (node_modules/@angular/core/fesm2015/core.mjs:2526:22)
      at callHooks (node_modules/@angular/core/fesm2015/core.mjs:2495:17)
      at executeInitAndCheckHooks (node_modules/@angular/core/fesm2015/core.mjs:2446:9)
      at refreshView (node_modules/@angular/core/fesm2015/core.mjs:9479:21)
      at renderComponentOrTemplate (node_modules/@angular/core/fesm2015/core.mjs:9578:9)
      at tickRootContext (node_modules/@angular/core/fesm2015/core.mjs:10809:9)
      at detectChangesInRootView (node_modules/@angular/core/fesm2015/core.mjs:10834:5)
      at RootViewRef.detectChanges (node_modules/@angular/core/fesm2015/core.mjs:21757:9)
      at ComponentFixture._tick (node_modules/@angular/core/fesm2015/testing.mjs:141:32)
      at node_modules/@angular/core/fesm2015/testing.mjs:154:22
      at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:407:30) 
      at ProxyZoneSpec.Object.<anonymous>.ProxyZoneSpec.onInvoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:3765:43)
      at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:406:56) 
      at Object.onInvoke (node_modules/@angular/core/fesm2015/core.mjs:25826:33)
      at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:406:56) 
      at Zone.Object.<anonymous>.Zone.run (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:167:47)
      at NgZone.run (node_modules/@angular/core/fesm2015/core.mjs:25680:28)
      at ComponentFixture.detectChanges (node_modules/@angular/core/fesm2015/testing.mjs:153:25)
      at src/app/modules/auth/login/login.component.spec.ts:36:13
      at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:407:30) 
      at ProxyZoneSpec.Object.<anonymous>.ProxyZoneSpec.onInvoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:3765:43)
      at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:406:56) 
      at Zone.Object.<anonymous>.Zone.run (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:167:47)
      at Object.wrappedFunc (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:4250:34)

I already tried to import StoreModule.forRoot(reducers), but if I rerun the test I get another error:

Unexpected value 'StoreRootModule' imported by the module 'DynamicTestModule'. Please add an @NgModule annotation.

I tried:

providers: [
        {
          provide: Store,
          useValue: {
            pipe: of({})
          }
        }
]

and

providers: [
        {
          provide: Store,
          useClass: MockStore
        }
]

I also followed the guide on the NgRx website on how to mock a store NgRx Testing. Neither worked.

My Angular project is running version 13.0.0 and Jest is running version 27.4.3.

If someone could help me with this, I would really appreciate that.



Solution 1:[1]

I copy pasted your code and adapted the lines I didn't have access to but my tests failed with another error

NullInjectorError: StaticInjectorError(DynamicTestModule)[LoginComponent -> FormBuilder]: 
  StaticInjectorError(Platform: core)[LoginComponent -> FormBuilder]: 
    NullInjectorError: No provider for FormBuilder!

Adding ReactiveFormsModule to the import section of the testfile solved this and the test passed

imports: [ReactiveFormsModule],

I don't think it's supposed to make a difference but you could also try this shorter syntax:

this.isAuthenticated$ = this.store.select(selectIsAuthenticated);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1