9
votes

afterEach(() => { fixture.destroy(); });I am currently trying to write tests for my ngrx based angular 7 application. The problem is that my test fails with the error Uncaught TypeError: Cannot read property 'xxxx' of undefined thrown. Here's how my test file looks like.

explore-products.component.spec.ts

import { async, ComponentFixture, TestBed } from "@angular/core/testing";

import { ExploreProductsComponent } from "./explore-products.component";
import { provideMockStore, MockStore } from "@ngrx/store/testing";
import { IAppState } from "src/app/store/state/app.state";
import { Store, StoreModule } from "@ngrx/store";
import { appReducers } from "src/app/store/reducers/app.reducer";

describe("ExploreProductsComponent", () => {
  let component: ExploreProductsComponent;
  let fixture: ComponentFixture<ExploreProductsComponent>;
  let store: MockStore<IAppState>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ExploreProductsComponent],
      providers: [provideMockStore()],
      imports: [StoreModule.forRoot(appReducers)]
    });

    store = TestBed.get(Store);
  });

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

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

The only should create test is throwing the error. The error is being throwing by the selector somehow, which means the xxxx property is not initialized but I am not sure how to resolve it. Here's what my component looks like.

explore-products.component.ts

import { Component, OnInit, OnDestroy } from "@angular/core";
import { IProduct } from "src/app/models/product";
import { environment } from "src/environments/environment";
import { selectProducts } from "../../store/selectors/product";
import { Store, select } from "@ngrx/store";
import { IAppState } from "src/app/store/state/app.state";
import { Subscription } from "rxjs";

@Component({
  selector: "app-explore-products",
  templateUrl: "./explore-products.component.html",
  styleUrls: ["./explore-products.component.css"]
})
export class ExploreProductsComponent implements OnInit, OnDestroy {
  public productsLoading = true;
  public endpoint = environment.apiEndpoint;

  private productsSelector = this.store.pipe(select(selectProducts));

  public products: IProduct[];
  private subscriptionsArr: Subscription[] = [];

  constructor(private store: Store<IAppState>) {}

  ngOnInit() {
    this.subscriptions();
  }
  subscriptions() {
    const subcriberProduct = this.productsSelector.subscribe(products => {
      this.products = products;
      if (this.products !== null) {
        this.toggleLoadingSign(false);
      }
    });
    this.subscriptionsArr.push(subcriberProduct);
  }
  toggleLoadingSign(toggleOption: boolean) {
    this.productsLoading = toggleOption;
  }
  ngOnDestroy() {
    for (const subscriber of this.subscriptionsArr) {
      subscriber.unsubscribe();
    }
  }
}

Please let me know if I can provide any other information.

Update

The problem is with AppState. The error is thrown since the state is not initialized which causes the error to occur i.e state.xxxx is undefined. The error sometimes randomly doesn't occur. I am not sure how to fix this.

The same problem is also mentioned here. But no solution

3

3 Answers

12
votes

For me adding this line of code afterEach(() => { fixture.destroy(); }); in all my spec files injecting provideMockStore() fixed this issue intermittently triggering.

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

  beforeEach(async(() => {
    const state = { featureKey: { property: value } }; // The state of your feature snippet
    TestBed.configureTestingModule({
      providers: [
        provideMockStore({ initialState: state }),
      ]
    }).compileComponents();
  }));

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

  afterEach(() => { fixture.destroy(); });

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

For me adding:

  afterEach(() => {
     fixture.destroy();
  });

to every spec file of a component solved the issue.

1
votes

You can try something like this. See how I've created mock store and used it. Added single line comments (with ************) wherever code was updated:

import { async, ComponentFixture, TestBed } from "@angular/core/testing";

import { ExploreProductsComponent } from "./explore-products.component";
import { provideMockStore, MockStore } from "@ngrx/store/testing";
import { IAppState } from "src/app/store/state/app.state";
import { Store, StoreModule } from "@ngrx/store";
import { appReducers } from "src/app/store/reducers/app.reducer";

describe("ExploreProductsComponent", () => {
  let component: ExploreProductsComponent;
  let fixture: ComponentFixture<ExploreProductsComponent>;
  //Update the store def.************
  let store: MockStore<any>;

  beforeEach( async(() => { //*****************UPDATE
    TestBed.configureTestingModule({
      declarations: [ExploreProductsComponent],
      providers: [provideMockStore()],
      //Change to imports************
      imports: [StoreModule.forRoot({})]
    }).compileComponents();//*****************UPDATE
    //Removed this
    //store = TestBed.get(Store);************

  }));//*****************UPDATE

  beforeEach(() => {
    fixture = TestBed.createComponent(ExploreProductsComponent);
    //Get store instance************
    store = fixture.debugElement.injector.get(Store);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it("should create", () => {
    //Spy on store actions************
    const spy = spyOn(store, 'dispatch');
   //Test is store is called properly************
   expect(spy).toHaveBeenCalledWith(Your params)
    expect(component).toBeTruthy();
  });
});