Search code examples
angularunit-testingkarma-jasminengrxngrx-store

How to detect store change in component after dispatch NgRx Unit testing


After changing the the state from 'light' to 'dark' it's not gonna change it in component. It will keep the state as 'light' even though I have dispatched an action which is gonna change it to dark. Is there a way to detect it in component?

Here is the unit test file

app.component.spec.ts

describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;
  let compiled: HTMLElement;
  let store: MockStore;

  const initialState = {
    theme: fromTheme.initialState,
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [AppComponent],
      imports: [AppRoutingModule],
      providers: [provideMockStore({ initialState })],
    }).compileComponents();

    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    compiled = fixture.debugElement.nativeElement;
    store = TestBed.get<MockStore>(MockStore);
  });

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

  it('should have a class with theme', () => {
    expect(compiled.classList.contains('light')).toBeTrue();

    store.dispatch(fromTheme.changeTheme({ payload: 'dark' }));

    fixture.detectChanges();

    expect(compiled.classList.contains('dark')).toBeTrue();
  });
});

Here is the component file

app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  host: {
    '[class.light]': 'theme === "light"',
    '[class.dark]': 'theme === "dark"',
  },
})
export class AppComponent implements OnInit, OnDestroy {
  theme: ThemeType = 'light';

  themeSubscription: Subscription | null = null;

  constructor(
    private store: Store<AppStore>,
  ) {}

  ngOnInit() {
    this.themeSubscription = this.store.select(selectTheme).subscribe((themeState) => {
      this.theme = themeState.theme;

      if (!themeState.isChanged) {
        const preferredTheme = getPreferredTheme();

        this.store.dispatch(changeTheme({ payload: preferredTheme }));
      }
    });
  }

  ngOnDestroy() {
    this.themeSubscription?.unsubscribe();
  }
}

Solution

  • There was no need to provide a mock store, instead of that, we can provide the real store like this

    let store: Store<AppStore>;
    
    beforeEach(() => {
      TestBed.configureTestingModule({
        declarations: [AppComponent],
        imports: [AppRoutingModule, StoreModule.forRoot(appReducer)],
      });
    
      fixture = TestBed.createComponent(AppComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
      compiled = fixture.debugElement.nativeElement;
      store = TestBed.get<Store>(Store);
    });
    

    Then the component will listen for changes in this store, and the dispatch will work.

    Test Examples:

    it('should have a class with theme', () => {
      store.select(fromTheme.selectTheme)
        .subscribe((themeState) => {
          expect(compiled.classList.contains(themeState.theme)).toBeTrue();
        });
    });
    

    it('should be able to dispatch the changeTheme', () => {
      const newTheme = 'dark';
    
      store.dispatch(fromTheme.changeTheme({ payload: newTheme }));
    
      expect(compiled.classList.contains(newTheme)).toBeTrue();
    });