Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

when testing ngrx selectors in an angular project unit test execution is flaky #24

Open
Olddude opened this issue Mar 1, 2022 · 0 comments

Comments

@Olddude
Copy link

Olddude commented Mar 1, 2022

Hello dear community,
we encounter flakiness when executing unit tests in our project.
we would appreciate any help or input on that issue.
to reproduce that issue you will need to run the npm run test command multiple times - about 10x should be enough.

Issue

do-something.effects.spec.ts and do-something.selectors.spec.ts unit tests seem to run into fixture data conflict when executed multiple times.

Code

do-something.selectors.spec.fixture.ts

import { DEFAULT_ROUTER_FEATURENAME, RouterReducerState } from '@ngrx/router-store';

import { IDoSomethingState } from '../../models/do-something-state';
import { Feature } from '../../models/event';
import { Foo } from '../../models/foo';
import * as fromDoSomething from './do-something.reducer';

interface ITestData {
  readonly foo: Foo;
}

const testData: ITestData = {
  foo: new Foo(
    'second'
  )
};


export class DoSomethingSelectorsFixture {
  state: IDoSomethingState;

  getStateForFeature() {
    const mockRouterUrl = '/tab2/restore';
    return {
      [DEFAULT_ROUTER_FEATURENAME]: {
        state: {
          url: mockRouterUrl
        }
      } as RouterReducerState,
      [Feature.DoSomething]: {
        ...fromDoSomething.initialState,
        [mockRouterUrl]: this.state
      }
    };
  }

  setupDoSomethingState() {
    this.state = {
      foo: testData.foo,
      bar: undefined
    } as IDoSomethingState;
  }
}

do-something.selectors.spec.ts

import { Foo } from '../../models/foo';
import { selectFoo } from './do-something.selectors';
import { DoSomethingSelectorsFixture } from './do-something.selectors.spec.fixture';

describe('DoSomethingFoo Selectors', () => {
  describe('select foo', () => {
    it('should return foo', () => {

      // Arrange
      const fixture = new DoSomethingSelectorsFixture();
      fixture.setupDoSomethingState();

      // Act
      const result = selectFoo(fixture.getStateForFeature());

      // Assert
      expect(result).not.toBeNull();
      expect(result).not.toBeUndefined();
      expect(result).toEqual(fixture.state.foo as Foo);
    });
  });
});

do-something.effects.spec.fixture.ts

import { Bar, IBar } from '../../models/bar';
import { Foo } from '../../models/foo';

interface Fixture {
  readonly foo: Foo,
  readonly bar: IBar
}

export const fixture: Fixture = {
  foo: new Foo(
    'first'
  ),
  bar: new Bar(
    123
  )
};

do-something.effects.spec.ts

import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { DEFAULT_ROUTER_FEATURENAME } from '@ngrx/router-store';
import { TypedAction } from '@ngrx/store/src/models';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { Observable, of } from 'rxjs';

import { DoSomethingApiService } from '../../api/do-something-api.service';
import { Feature } from '../../models/event';
import {
  loadBar,
  loadBarSuccess,
} from './do-something.actions';
import { DoSomethingEffects } from './do-something.effects';
import { fixture } from './do-something.effects.spec.fixture';
import {
  selectFoo
} from './do-something.selectors';

describe('DoSomethingFooEffects', () => {
  let actions$: Observable<TypedAction<string>>;
  let effects: DoSomethingEffects;
  let store: MockStore;
  let apiService: DoSomethingApiService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule.withRoutes([
          {
            path: 'tab2',
            children: []
          }
        ])
      ],
      providers: [
        DoSomethingEffects,
        provideMockActions(() => actions$),
        provideMockStore({
          initialState: {
            [DEFAULT_ROUTER_FEATURENAME]: {
              state: {
                url: '/myUrl',
              },
            },
            [Feature.DoSomething]: {
              ['/myUrl']: undefined
            }
          },
        }),
        {
          provide: DoSomethingApiService,
          useFactory: (): Partial<DoSomethingApiService> => ({
            getBarOfFoo: () => of(undefined)
          }),
        }
      ],
    });

    effects = TestBed.inject(DoSomethingEffects);
    store = TestBed.inject(MockStore);
    apiService = TestBed.inject(DoSomethingApiService);
  });

  describe('loadBarOfFoo', () => {
    it('should return loadBarOfFooSuccess on success', async () => {
      // Arrange
      store.overrideSelector(selectFoo, fixture.foo);
      spyOn(
        apiService,
        'getBarOfFoo'
      ).and.returnValue(of(fixture.bar));
      actions$ = of(
        loadBar({ url: 'test' })
      );

      // Act
      const output =
        await effects.loadBarOfFoo$.toPromise();

      // Assert
      expect(output).toEqual(
        loadBarSuccess({
          url: '/myUrl',
          bar: fixture.bar,
        })
      );
    });
  });
});

Karma Unit Test Log Output

Chrome Headless 98.0.4758.109 (Mac OS 10.15.7): Executed 0 of 2 SUCCESS (0 
Chrome Headless 98.0.4758.109 (Mac OS 10.15.7): Executed 1 of 2 SUCCESS (0 
Chrome Headless 98.0.4758.109 (Mac OS 10.15.7) DoSomethingFoo Selectors select foo should return foo FAILED
        Error: Expected $.name = 'first' to equal 'second'.
            at <Jasmine>
            at UserContext.<anonymous> (src/app/features/do-something/do-something.selectors.spec.ts:19:22)
            at ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:400:1)
            at ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/zone-testing.js:301:43)
Chrome Headless 98.0.4758.109 (Mac OS 10.15.7): Executed 2 of 2 (1 FAILED) (0 secs / 0.055 secs)
Chrome Headless 98.0.4758.109 (Mac OS 10.15.7) DoSomethingFoo Selectors select foo should return foo FAILED
        Error: Expected $.name = 'first' to equal 'second'.
            at <Jasmine>
            at UserContext.<anonymous> (src/app/features/do-something/do-something.selectors.spec.ts:19:22)
            at ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:400:1)
            at ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/zone-testi
Chrome Headless 98.0.4758.109 (Mac OS 10.15.7): Executed 2 of 2 (1 FAILED) (0.072 secs / 0.055 secs)
TOTAL: 1 FAILED, 1 SUCCESS
TOTAL: 1 FAILED, 1 SUCCESS

Repository

https:/Olddude/flaky-selector-tests

Technology

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant