简体   繁体   中英

Angular Unit Testing - How to trigger another emit from an injected service?

I have a component that does the following:

  1. In the constructor, it takes the query parameters and parses them into the given quarter and year

  2. In ngOnInit, it subscribes to a service that returns a list of quarters and years. In the subscription's callback, it uses the values of the quarter and year to assign a value to either the chosen quarter and year from the query params, or the first in the list if they are not valid.

In my unit test, I am able to set the params (or at least the snapshot) but I want to re-trigger the callback inside the subscribe method and assert some expectations. I am not sure how to re-trigger the callback in the subscribe method.

Here is the relevant code of my component:

@Component({
    ...
})
export class MyComponent implements OnInit {
quarter: number;
year: number;
paramQuarter: string;
selectedQuarter: IQuarter;
quartersList: IQuarter[];

constructor(
            private quarterService: QuarterService,
            private activatedRoute: ActivatedRoute,
            private router : Router) {

    // if no query param is set, set as the current quarter and year from moment.js
    this.paramQuarter = this.activatedRoute.snapshot.queryParams['quarter'] 
        || moment().quarter() + '-' + moment().year();

}

ngOnInit(): void {

    this.quarterService.getQuarters().subscribe(response => {
        this.quartersList = response;
        /// ----------------------------------
        /// The code I want to test is in here
        /// ----------------------------------
        const [quarter, year] = this.paramQuarter.split("-").map(string => parseInt(string));
        // get the correct quarter based on URL params, or the current one if the url param is malformed
        this.selectedQuarter: IQuarter = this.quartersList
            .filter(q => q.quarter === quarter && q.year === year)[0]
            || this.quartersList[0];
    });
}

}

And of the test:

const MOCK_QUARTERS = [
{
    quarter: 2,
    year: 2020,
    startDate: '2020-04-01',
    endDate: '2020-06-30',
},
{
    quarter: 1,
    year: 2020,
    startDate: '2020-01-01',
    endDate: '2020-03-31',
}
]

describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let quarterService: QuarterService;
let router : Router;
let activatedRoute : ActivatedRoute;

const MockQuarterService = {
    getQuarters: jasmine.createSpy('getQuarters').and.returnValue(
    of(MOCK_QUARTERS)
    )
}

beforeEach(async(() => {
    TestBed.configureTestingModule({
    declarations: [
        MyComponent,
    ],
    imports: [
        SharedModule,
        RouterTestingModule,
        ...HttpClientTestingImports,
    ],
    providers: [
        ...HttpClientTestingProviders,
        { provide: QuarterService, useValue: MockQuarterService },
        {
        provide: ActivatedRoute,
        useValue: {
            queryParams: of({ quarter: '2-2020' }),
            snapshot: {
            queryParams: { quarter: '2-2020' }
            }
        }
        },
    ]
    })
    .compileComponents();
}));

beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    quarterService = TestBed.inject(QuarterService);
    router = TestBed.inject(Router);
    activatedRoute = TestBed.inject(ActivatedRoute);
    fixture.detectChanges();
});

fit('should set the current quarter from correctly formed URL params', async(() => {
    // set from the params in the beforeEach callback
    expect(component.selectedQuarter).toEqual(MOCK_QUARTERS[0]);
    spyOn(component, 'updateQuarterSelection').and.callThrough();

    let actRoute : ActivatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
    actRoute.queryParams = of( {quarter: `${MOCK_QUARTERS[1].quarter}-${MOCK_QUARTERS[1].year}` } );
    actRoute.snapshot.queryParams.quarter = `${MOCK_QUARTERS[1].quarter}-${MOCK_QUARTERS[1].year}`;
    /// ----------------------------------
    // Somehow need the callback of the quarterService.getQuarters().subscribe() 
    // method to retrigger here
    /// ----------------------------------
    fixture.detectChanges();
    console.log(component);
    
    expect(quarterService.getQuarters).toHaveBeenCalledTimes(2);
    expect(component.updateQuarterSelection).toHaveBeenCalledTimes(2);
    expect(component.selectedQuarter).toEqual(MOCK_QUARTERS[1]);
}));

});

For a quick unblock, you can simply call ngOnInit again.

fit('should set the current quarter from correctly formed URL params', async(() => {
    // set from the params in the beforeEach callback
    expect(component.selectedQuarter).toEqual(MOCK_QUARTERS[0]);
    spyOn(component, 'updateQuarterSelection').and.callThrough();

    let actRoute : ActivatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
    actRoute.queryParams = of( {quarter: `${MOCK_QUARTERS[1].quarter}-${MOCK_QUARTERS[1].year}` } );
    actRoute.snapshot.queryParams.quarter = `${MOCK_QUARTERS[1].quarter}-${MOCK_QUARTERS[1].year}`;
    
    component.ngOnInit();    

    fixture.detectChanges();
    console.log(component);
    
    expect(quarterService.getQuarters).toHaveBeenCalledTimes(2);
    expect(component.updateQuarterSelection).toHaveBeenCalledTimes(2);
    expect(component.selectedQuarter).toEqual(MOCK_QUARTERS[1]);
}));

For a more elaborate answer, the first fixture.detectChanges() you call is when ngOnInit is called. So you can be strategic with your beforeEach 's of when the constructor is called (createComponent) (I won't show this method because it requires more bookkeeping). We can take advantage of a BehaviorSubject to retrigger some subscriptions.

import { BehaviorSubject } from 'rxjs';
....
// replace any with Quarter interface
const mockQuarters = new BehaviorSubject<any>(MOCK_QUARTERS);
const MockQuarterService = {
    getQuarters: () => mockQuarters, // return BehaviorSubject
}

...
beforeEach(async(() => {
    TestBed.configureTestingModule({
    declarations: [
        MyComponent,
    ],
    imports: [
        SharedModule,
        RouterTestingModule,
        ...HttpClientTestingImports,
    ],
    providers: [
        ...HttpClientTestingProviders,
        { provide: QuarterService, useValue: MockQuarterService },
        {
        provide: ActivatedRoute,
        useValue: {
            queryParams: of({ quarter: '2-2020' }),
            snapshot: {
            queryParams: { quarter: '2-2020' }
            }
        }
        },
    ]
    })
    .compileComponents();
}));

beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    quarterService = TestBed.inject(QuarterService);
    router = TestBed.inject(Router);
    activatedRoute = TestBed.inject(ActivatedRoute);
    fixture.detectChanges();
});

fit('should set the current quarter from correctly formed URL params', async(() => {
    // set from the params in the beforeEach callback
    expect(component.selectedQuarter).toEqual(MOCK_QUARTERS[0]);
    spyOn(component, 'updateQuarterSelection').and.callThrough();

    let actRoute : ActivatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
    actRoute.queryParams = of( {quarter: `${MOCK_QUARTERS[1].quarter}-${MOCK_QUARTERS[1].year}` } );
    actRoute.snapshot.queryParams.quarter = `${MOCK_QUARTERS[1].quarter}-${MOCK_QUARTERS[1].year}`;

    // this should retrigger the subscription
    mockQuarters.next(MOCK_QUARTERS);
    
    fixture.detectChanges();
    console.log(component);
    
    expect(quarterService.getQuarters).toHaveBeenCalledTimes(2);
    expect(component.updateQuarterSelection).toHaveBeenCalledTimes(2);
    expect(component.selectedQuarter).toEqual(MOCK_QUARTERS[1]);
}));




The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM