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

docs: how to mock with MockBuilder.provide #7801

Open
satanTime opened this issue Dec 25, 2023 Discussed in #7792 · 0 comments
Open

docs: how to mock with MockBuilder.provide #7801

satanTime opened this issue Dec 25, 2023 Discussed in #7792 · 0 comments

Comments

@satanTime
Copy link
Member

Discussed in #7792

Originally posted by kpeters-cbsi December 23, 2023
What's the recommended way of mocking a ComponentStore instance?

I have the following Component:

import { Component, OnInit } from '@angular/core';
import { WorkerJobStatusDetailComponent } from './component/worker-job-status-detail.component';
import { WorkerJobStatusDetailStore } from 'src/app/features/worker-job-status/worker-job-status-detail/store/worker-job-status-detail.store';
import { WorkerJobStatusService } from '../worker-job-status.service';
import { ActivatedRoute } from '@angular/router';
import { Observable, of, switchMap, tap, zip } from 'rxjs';

@Component({
  selector: 'app-worker-job-status-detail-container',
  standalone: true,
  imports: [WorkerJobStatusDetailComponent],
  template: '<app-worker-job-status-detail />',
  styles: `
    :host {
      display: block;
      width: 100%;
    }
  `,
  providers: [WorkerJobStatusDetailStore],
})
export class WorkerJobStatusDetailContainerComponent implements OnInit {
  constructor(
    private readonly _workerJobStatusDetailStore: WorkerJobStatusDetailStore,
    private readonly _backend: WorkerJobStatusService,
    private readonly _route: ActivatedRoute
  ) {
    console.debug('WorkerJobStatusDetailContainerComponent::constructor', {
      _workerJobStatusDetailStore,
      _backend,
      _route,
    });
  }
  ngOnInit(): void {
    console.debug('WorkerJobStatusDetailContainerComponent::ngOnInit');
    const getWorkerJobStatus$ = (
      this._route.params as Observable<{ jobId: string }>
    ).pipe(
      tap((params) => console.debug('Goot params', params)),
      switchMap(({ jobId }) =>
        zip(of(jobId), this._backend.getWorkerJobStatus(jobId))
      )
    );
    getWorkerJobStatus$.subscribe(([jobId, status]) => {
      console.debug('In subscribe', { jobId, status });
      this._workerJobStatusDetailStore.setJobId(jobId);
      this._workerJobStatusDetailStore.setStatus(status);
    });
  }
}

The store:

import { Injectable } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
import { WorkerJobStatus } from '../../types';

export interface WorkerJobStatusDetailState {
  /**
   * A unique ID for this record
   */
  jobId: string;
  /**
   * The status of the given job
   */
  status: WorkerJobStatus;
}

const initialState: WorkerJobStatusDetailState = {
  jobId: '',
  status: {
    worker: '',
    job: '',
    jobStatus: '',
    expiration: new Date(),
    statusDate: new Date(),
  },
};

@Injectable()
export class WorkerJobStatusDetailStore extends ComponentStore<WorkerJobStatusDetailState> {
  constructor() {
    super(initialState);
  }

  /*
   *** SELECTORS ***
   * These produce a subset of the total component state
   */
  readonly jobId$ = this.select(({ jobId }) => jobId);
  readonly status$ = this.select(({ status }) => status);

  readonly setJobId = this.updater(
    (state, jobId: string | null): WorkerJobStatusDetailState => ({
      ...state,
      jobId: jobId || '',
    })
  );

  readonly setStatus = this.updater(
    (state, status: WorkerJobStatus | null): WorkerJobStatusDetailState => ({
      ...state,
      status: status || initialState.status,
    })
  );
}

And the corresponding test:

import { MockBuilder, MockInstance, MockRender } from 'ng-mocks';
import { WorkerJobStatusDetailContainerComponent } from './worker-job-status-detail-container.component';
import { WorkerJobStatusDetailStore } from './store/worker-job-status-detail.store';
import { WorkerJobStatusService } from '../worker-job-status.service';
import { Observable, of } from 'rxjs';
import { WorkerJobStatus } from '../types';
import { ActivatedRoute, Params } from '@angular/router';
import { randomDate } from '@common/testing/util';

const mockGetWorkerJobStatus = jest.fn();
const mockSetJobId = jest.fn();
const mockSetStatus = jest.fn();
let mockParams: Observable<Params> = of({ mock: 'params' });

class mockActivatedRoute {
  get params() {
    return mockParams;
  }
}
describe('WorkerJobStatusDetailContainerComponent', () => {
  MockInstance.scope();
  beforeEach(() =>
    MockBuilder(WorkerJobStatusDetailContainerComponent)
      .provide({
        provide: WorkerJobStatusService,
        useValue: { getWorkerJobStatus: mockGetWorkerJobStatus },
      })
      .provide({
        provide: WorkerJobStatusDetailStore,
        useValue: {
          setJobId: mockSetJobId,
          setStatus: mockSetStatus,
        },
      })
      .provide({
        provide: ActivatedRoute,
        useValue: new mockActivatedRoute(),
      })
  );

  beforeAll(() => {
    mockGetWorkerJobStatus.mockImplementation(
      (): Observable<WorkerJobStatus> =>
        of({
          worker: '',
          job: '',
          statusDate: new Date(),
          expiration: new Date(),
          jobStatus: '',
        })
    );
    mockSetJobId.mockImplementation((jobId) =>
      console.debug('mockSetJobId', jobId)
    );
    mockSetStatus.mockImplementation((status) =>
      console.debug('mockSetStatus', status)
    );
  });

  it('should create', () => {
    const fixture = MockRender(WorkerJobStatusDetailContainerComponent);
    expect(fixture.componentInstance).toBeTruthy();
  });

  it('should load job status', () => {
    const jobId = 'worker|job';
    const expected: WorkerJobStatus = {
      worker: 'dummy-worker',
      job: 'dummy-job',
      expiration: randomDate(),
      jobStatus: `dummy-job-status`,
      statusDate: randomDate(),
    };
    mockGetWorkerJobStatus.mockReturnValue(of(expected));
    mockParams = of({ jobId });
    MockRender(WorkerJobStatusDetailContainerComponent);
    console.debug(
      'mockGetWorkerJobStatus calls',
      mockGetWorkerJobStatus.mock.calls
    );
    console.debug('mockSetJobId calls', mockSetJobId.mock.calls);
    console.debug('mockSetStatus calls', mockSetStatus.mock.calls);
    expect(mockGetWorkerJobStatus).toHaveBeenCalledWith(jobId);
    expect(mockSetJobId).toHaveBeenCalledWith(jobId);
    expect(mockSetStatus).toHaveBeenCalledWith(expected);
  });
});

When I run the test, the mocked ComponentStore instance is not being injected into the component. Instead it appears that an unmocked instance is being inserted. What am I doing wrong?

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