CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ngrx--store

RxJS powered Redux state management for Angular applications

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

testing-utilities.mddocs/

Testing Utilities

Testing utilities provide comprehensive mock implementations and testing helpers for NgRx Store, enabling effective unit testing of components, services, and state management logic.

Capabilities

Provide Mock Store

Creates mock store providers for testing with TestBed configuration.

/**
 * Creates mock store providers for testing environments
 * @param config - Mock store configuration with initial state and selectors
 * @returns Provider array for TestBed configuration
 */
function provideMockStore<T = any>(
  config?: MockStoreConfig<T>
): (ValueProvider | ExistingProvider | FactoryProvider)[];

/**
 * Configuration for mock store setup
 */
interface MockStoreConfig<T> {
  /** Initial state for mock store */
  initialState?: T;
  /** Mock selectors with predefined return values */
  selectors?: MockSelector[];
}

Usage Examples:

import { TestBed } from "@angular/core/testing";
import { provideMockStore, MockStore } from "@ngrx/store/testing";
import { selectUser, selectUserName } from "./user.selectors";

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

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [UserComponent],
      providers: [
        provideMockStore({
          initialState: {
            user: {
              entities: [
                { id: '1', name: 'John Doe', email: 'john@example.com' },
                { id: '2', name: 'Jane Smith', email: 'jane@example.com' }
              ],
              selectedUserId: '1',
              loading: false,
              error: null
            }
          },
          selectors: [
            { selector: selectUser, value: { id: '1', name: 'John Doe' } },
            { selector: selectUserName, value: 'John Doe' }
          ]
        })
      ]
    }).compileComponents();

    store = TestBed.inject(MockStore);
    fixture = TestBed.createComponent(UserComponent); 
    component = fixture.componentInstance;
  });

  it('should display user name', () => {
    fixture.detectChanges();
    expect(component.userName()).toBe('John Doe');
  });
});

Create Mock Store

Creates a standalone mock store instance without TestBed for service and effect testing.

/**
 * Creates mock store instance outside of TestBed
 * @param config - Mock store configuration
 * @returns MockStore instance for testing
 */
function createMockStore<T>(config?: MockStoreConfig<T>): MockStore<T>;

Usage Examples:

import { createMockStore } from "@ngrx/store/testing";
import { UserEffects } from "./user.effects";

describe('UserEffects', () => {
  let effects: UserEffects;
  let store: MockStore;
  let userService: jasmine.SpyObj<UserService>;

  beforeEach(() => {
    const userServiceSpy = jasmine.createSpyObj('UserService', ['loadUser', 'updateUser']);
    
    store = createMockStore({
      initialState: {
        user: { entities: [], loading: false, error: null }
      }
    });

    TestBed.configureTestingModule({
      providers: [
        UserEffects,
        { provide: Store, useValue: store },
        { provide: UserService, useValue: userServiceSpy }
      ]
    });

    effects = TestBed.inject(UserEffects);
    userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
  });

  it('should load user data', () => {
    userService.loadUser.and.returnValue(of({ id: '1', name: 'Test User' }));
    
    store.dispatch(loadUser({ id: '1' }));
    
    expect(userService.loadUser).toHaveBeenCalledWith('1');
  });
});

Mock Store Class

Enhanced Store implementation with testing-specific methods for overriding selectors and managing state.

/**
 * Mock implementation of Store for testing
 */
class MockStore<T> extends Store<T> {
  /**
   * Override selector return value for testing
   * @param selector - Selector to override
   * @param value - Value to return from selector
   */
  overrideSelector<Result>(
    selector: MemoizedSelector<any, Result>,
    value: Result
  ): void;
  
  /**
   * Refresh mock state and notify subscribers
   */
  refreshState(): void;
  
  /**
   * Set mock state directly
   * @param nextState - New state to set
   */
  setState(nextState: T): void;
  
  /**
   * Reset all selector overrides
   */
  resetSelectors(): void;
}

Usage Examples:

import { MockStore } from "@ngrx/store/testing";
import { selectUserPreferences, selectTheme } from "./ui.selectors";

describe('UI Component Tests', () => {
  let store: MockStore;
  let component: ThemeToggleComponent;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [provideMockStore()]
    });

    store = TestBed.inject(MockStore);
    component = TestBed.inject(ThemeToggleComponent);
  });

  it('should handle theme changes', () => {
    // Override selector return value
    store.overrideSelector(selectTheme, 'dark');
    store.refreshState();

    expect(component.currentTheme()).toBe('dark');

    // Change selector value during test
    store.overrideSelector(selectTheme, 'light');
    store.refreshState();

    expect(component.currentTheme()).toBe('light');
  });

  it('should handle complex state changes', () => {
    const mockPreferences = {
      theme: 'auto',
      language: 'en',
      notifications: true
    };

    store.overrideSelector(selectUserPreferences, mockPreferences);
    store.refreshState();

    expect(component.preferences()).toEqual(mockPreferences);

    // Direct state manipulation
    store.setState({
      ...store.state(),
      ui: {
        preferences: { ...mockPreferences, theme: 'dark' }
      }
    });

    expect(component.preferences().theme).toBe('dark');
  });
});

Mock Selector Interface

Configuration for mocking individual selectors with predefined return values.

/**
 * Configuration for mocking individual selectors
 */
interface MockSelector {
  /** The selector to mock */
  selector: MemoizedSelector<any, any> | string;
  /** The value to return from the selector */
  value: any;
}

Usage Examples:

import { selectAllUsers, selectActiveUsers, selectUserCount } from "./user.selectors";

const mockSelectors: MockSelector[] = [
  { 
    selector: selectAllUsers, 
    value: [
      { id: '1', name: 'John', active: true },
      { id: '2', name: 'Jane', active: false }
    ]
  },
  { 
    selector: selectActiveUsers, 
    value: [{ id: '1', name: 'John', active: true }] 
  },
  { 
    selector: selectUserCount, 
    value: 2 
  }
];

TestBed.configureTestingModule({
  providers: [
    provideMockStore({ 
      selectors: mockSelectors 
    })
  ]
});

Testing Patterns

Component Testing

import { ComponentFixture, TestBed } from "@angular/core/testing";
import { MockStore } from "@ngrx/store/testing";
import { UserListComponent } from "./user-list.component";

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

  const initialState = {
    users: {
      entities: [],
      loading: false,
      error: null,
      filters: { search: '', role: 'all' }
    }
  };

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [UserListComponent],
      providers: [provideMockStore({ initialState })]
    }).compileComponents();

    store = TestBed.inject(MockStore);
    fixture = TestBed.createComponent(UserListComponent);
    component = fixture.componentInstance;
  });

  it('should display loading state', () => {
    store.setState({
      ...initialState,
      users: { ...initialState.users, loading: true }
    });
    
    fixture.detectChanges();
    
    expect(fixture.nativeElement.querySelector('.loading')).toBeTruthy();
  });

  it('should dispatch search action', () => {
    spyOn(store, 'dispatch');
    
    component.searchUsers('john');
    
    expect(store.dispatch).toHaveBeenCalledWith(
      searchUsers({ query: 'john' })
    );
  });

  it('should handle error state', () => {
    const errorMessage = 'Failed to load users';
    
    store.setState({
      ...initialState,
      users: { ...initialState.users, error: errorMessage }
    });
    
    fixture.detectChanges();
    
    expect(fixture.nativeElement.textContent).toContain(errorMessage);
  });
});

Service Testing

import { TestBed } from "@angular/core/testing";
import { createMockStore, MockStore } from "@ngrx/store/testing";
import { UserDataService } from "./user-data.service";

describe('UserDataService', () => {
  let service: UserDataService;
  let store: MockStore;

  beforeEach(() => {
    store = createMockStore({
      initialState: {
        users: { entities: [], selectedUserId: null }
      }
    });

    TestBed.configureTestingModule({
      providers: [
        UserDataService,
        { provide: Store, useValue: store }
      ]
    });

    service = TestBed.inject(UserDataService);
  });

  it('should get current user', (done) => {
    const mockUser = { id: '1', name: 'Test User' };
    
    store.overrideSelector(service.selectCurrentUser, mockUser);
    store.refreshState();

    service.getCurrentUser().subscribe(user => {
      expect(user).toEqual(mockUser);
      done();
    });
  });

  it('should load user by ID', () => {
    spyOn(store, 'dispatch');
    
    service.loadUser('123');
    
    expect(store.dispatch).toHaveBeenCalledWith(
      loadUser({ id: '123' })
    );
  });
});

Effect Testing

import { TestBed } from "@angular/core/testing";
import { provideMockActions } from "@ngrx/effects/testing";
import { Observable, of, throwError } from "rxjs";
import { MockStore, createMockStore } from "@ngrx/store/testing";
import { UserEffects } from "./user.effects";

describe('UserEffects', () => {
  let actions$: Observable<any>;
  let effects: UserEffects;
  let store: MockStore;
  let userService: jasmine.SpyObj<UserService>;

  beforeEach(() => {
    const userServiceSpy = jasmine.createSpyObj('UserService', ['loadUser']);
    
    TestBed.configureTestingModule({
      providers: [
        UserEffects,
        provideMockActions(() => actions$),
        { provide: UserService, useValue: userServiceSpy },
        { provide: Store, useValue: createMockStore() }
      ]
    });

    effects = TestBed.inject(UserEffects);
    store = TestBed.inject(MockStore);
    userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
  });

  it('should load user successfully', () => {
    const user = { id: '1', name: 'Test User' };
    const action = loadUser({ id: '1' });
    const outcome = loadUserSuccess({ user });

    actions$ = of(action);
    userService.loadUser.and.returnValue(of(user));

    effects.loadUser$.subscribe(result => {
      expect(result).toEqual(outcome);
    });
  });

  it('should handle load user error', () => {
    const error = new Error('Network error');
    const action = loadUser({ id: '1' });
    const outcome = loadUserFailure({ error: error.message });

    actions$ = of(action);
    userService.loadUser.and.returnValue(throwError(() => error));

    effects.loadUser$.subscribe(result => {
      expect(result).toEqual(outcome);
    });
  });
});

Selector Testing

import { selectUserSummary, selectFilteredUsers } from "./user.selectors";

describe('User Selectors', () => {
  const mockState = {
    users: {
      entities: [
        { id: '1', name: 'John', role: 'admin', active: true },
        { id: '2', name: 'Jane', role: 'user', active: false },
        { id: '3', name: 'Bob', role: 'user', active: true }
      ],
      filters: { role: 'user', active: true }
    }
  };

  it('should select user summary', () => {
    const result = selectUserSummary(mockState);
    
    expect(result).toEqual({
      total: 3,
      active: 2,
      byRole: { admin: 1, user: 2 }
    });
  });

  it('should select filtered users', () => {
    const result = selectFilteredUsers(mockState);
    
    expect(result).toHaveLength(1);
    expect(result[0]).toEqual({ id: '3', name: 'Bob', role: 'user', active: true });
  });

  it('should memoize selector results', () => {
    const result1 = selectUserSummary(mockState);
    const result2 = selectUserSummary(mockState);
    
    // Same reference due to memoization
    expect(result1).toBe(result2);
  });
});

Integration Testing

import { ComponentFixture, TestBed } from "@angular/core/testing";
import { Store } from "@ngrx/store";
import { MockStore, provideMockStore } from "@ngrx/store/testing";
import { UserFeatureComponent } from "./user-feature.component";

describe('User Feature Integration', () => {
  let component: UserFeatureComponent;
  let fixture: ComponentFixture<UserFeatureComponent>;
  let store: MockStore;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [UserFeatureComponent],
      providers: [
        provideMockStore({
          initialState: { users: { entities: [], loading: false } }
        })
      ]
    }).compileComponents();

    store = TestBed.inject(MockStore);
    fixture = TestBed.createComponent(UserFeatureComponent);
    component = fixture.componentInstance;
  });

  it('should handle complete user workflow', () => {
    spyOn(store, 'dispatch');

    // Initial load
    component.ngOnInit();
    expect(store.dispatch).toHaveBeenCalledWith(loadUsers());

    // Simulate loaded state
    store.setState({
      users: {
        entities: [{ id: '1', name: 'John' }],
        loading: false,
        selectedUserId: null
      }
    });
    fixture.detectChanges();

    // User selection
    component.selectUser('1');
    expect(store.dispatch).toHaveBeenCalledWith(selectUser({ id: '1' }));

    // User update
    component.updateUser({ id: '1', name: 'John Updated' });
    expect(store.dispatch).toHaveBeenCalledWith(
      updateUser({ id: '1', changes: { name: 'John Updated' } })
    );
  });
});

Additional Testing Components

Mock Reducer Manager

Mock implementation of ReducerManager for testing dynamic reducer scenarios.

/**
 * Mock implementation of ReducerManager for testing
 */
class MockReducerManager extends BehaviorSubject<ActionReducer<any, any>> {
  /** Mock current reducers */
  get currentReducers(): ActionReducerMap<any, any>;

  /** Mock methods for testing dynamic reducer management */
  addFeature(feature: StoreFeature<any, any>): void;
  addFeatures(features: StoreFeature<any, any>[]): void;
  removeFeature(feature: StoreFeature<any, any>): void;
  removeFeatures(features: StoreFeature<any, any>[]): void;
  addReducer(key: string, reducer: ActionReducer<any, any>): void;
  addReducers(reducers: ActionReducerMap<any, any>): void;
  removeReducer(key: string): void;
  removeReducers(featureKeys: string[]): void;
  ngOnDestroy(): void;
}

Usage Examples:

import { TestBed } from "@angular/core/testing";
import { MockReducerManager } from "@ngrx/store/testing";
import { ReducerManager } from "@ngrx/store";

describe('Dynamic Feature Service', () => {
  let service: DynamicFeatureService;
  let mockReducerManager: MockReducerManager;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        DynamicFeatureService,
        { provide: ReducerManager, useClass: MockReducerManager }
      ]
    });

    service = TestBed.inject(DynamicFeatureService);
    mockReducerManager = TestBed.inject(ReducerManager) as MockReducerManager;
  });

  it('should add feature reducer', () => {
    spyOn(mockReducerManager, 'addReducer');
    
    service.loadUserFeature();
    
    expect(mockReducerManager.addReducer).toHaveBeenCalledWith(
      'users',
      userReducer
    );
  });
});

Mock State

Mock implementation of the core State service for testing.

/**
 * Mock implementation of State service for testing
 */
class MockState<T> extends BehaviorSubject<T> {
  /** Mock state as Signal */
  readonly state: Signal<T>;

  /** Constructor takes initial state */
  constructor(initialState?: T);

  /** Mock lifecycle hook */
  ngOnDestroy(): void;
}

Usage Examples:

import { TestBed } from "@angular/core/testing";
import { MockState } from "@ngrx/store/testing";
import { State } from "@ngrx/store";

describe('State-dependent Service', () => {
  let service: StateService;
  let mockState: MockState<AppState>;

  beforeEach(() => {
    const initialState: AppState = {
      user: { id: null, name: '' },
      settings: { theme: 'light' }
    };

    TestBed.configureTestingModule({
      providers: [
        StateService,
        { provide: State, useValue: new MockState(initialState) }
      ]
    });

    service = TestBed.inject(StateService);
    mockState = TestBed.inject(State) as MockState<AppState>;
  });

  it('should react to state changes', () => {
    const newState: AppState = {
      user: { id: '123', name: 'John' },
      settings: { theme: 'dark' }
    };

    mockState.next(newState);

    expect(service.currentUser()).toEqual(newState.user);
  });
});

Mock Selectors

Configuration for overriding selector behavior in tests.

/**
 * Mock selector configuration for testing
 */
interface MockSelector {
  /** Selector function to override */
  selector: any;
  /** Value to return from selector */
  value: any;
}

/** Token for injecting mock selectors */
const MOCK_SELECTORS: InjectionToken<MockSelector[]>;

Usage Examples:

import { MockSelector, MOCK_SELECTORS } from "@ngrx/store/testing";

describe('Component with Custom Selectors', () => {
  const mockSelectors: MockSelector[] = [
    { selector: selectUserRole, value: 'admin' },
    { selector: selectUserPermissions, value: ['read', 'write', 'delete'] },
    { selector: selectAppConfig, value: { version: '1.0.0', debug: true } }
  ];

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [AdminComponent],
      providers: [
        provideMockStore({
          initialState: defaultState,
          selectors: mockSelectors
        })
      ]
    }).compileComponents();
  });

  it('should show admin features', () => {
    fixture.detectChanges();
    
    expect(component.userRole()).toBe('admin');
    expect(component.canDelete()).toBe(true);
  });
});

Manual Mock Store Creation

Alternative approach for creating mock stores without TestBed.

import { MockStore, createMockStore } from "@ngrx/store/testing";

// Create standalone mock store
const mockStore = createMockStore({
  initialState: { counter: 0 },
  selectors: [
    { selector: selectCount, value: 42 }
  ]
});

// Use in service testing
const counterService = new CounterService(mockStore);

// Override selectors dynamically
mockStore.overrideSelector(selectCount, 100);
mockStore.refreshState();

// Verify dispatch calls
spyOn(mockStore, 'dispatch');
counterService.increment();
expect(mockStore.dispatch).toHaveBeenCalledWith(increment());

Testing Patterns

Component Testing with Signals

describe('Signal-based Component', () => {
  let component: UserProfileComponent;
  let store: MockStore;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [UserProfileComponent],
      providers: [
        provideMockStore({
          initialState: { user: { name: 'John', email: 'john@test.com' } }
        })
      ]
    });

    store = TestBed.inject(MockStore);
    component = TestBed.createComponent(UserProfileComponent).componentInstance;
  });

  it('should update signal when state changes', () => {
    expect(component.userName()).toBe('John');

    store.setState({ user: { name: 'Jane', email: 'jane@test.com' } });
    
    expect(component.userName()).toBe('Jane');
  });
});

Async Testing with Mock Store

it('should handle async selectors', fakeAsync(() => {
  const asyncData$ = store.select(selectAsyncData);
  let result: any;

  asyncData$.subscribe(data => result = data);
  tick();

  expect(result).toEqual(initialAsyncData);

  store.overrideSelector(selectAsyncData, updatedAsyncData);
  store.refreshState();
  tick();

  expect(result).toEqual(updatedAsyncData);
}));

Best Practices

  1. Mock Configuration: Use provideMockStore with meaningful initial state and selectors
  2. Selector Overrides: Use overrideSelector to test different state scenarios
  3. Dispatch Verification: Verify that components dispatch correct actions with proper payloads
  4. State Management: Use setState for complex state changes during tests
  5. Effect Testing: Test effects separately using provideMockActions and mock services
  6. Integration Testing: Test complete workflows with realistic state transitions
  7. Signal Testing: Test Signal-based components with proper state updates
  8. Mock Services: Use MockReducerManager and MockState for testing internal NgRx services
  9. Async Testing: Use fakeAsync and tick() for testing asynchronous state changes

docs

action-creators.md

feature-management.md

index.md

module-configuration.md

reducer-creators.md

selectors.md

standalone-providers.md

store-service.md

testing-utilities.md

tile.json