Write angular2 tests and change the mock return values ​​- do DRY?

I am writing several tests for a service, and I am modifying the response from the mock functions to test various cases. At the moment, every time I want to change the layout response, I need to reset TestBed and configure the test module again, introducing my new Mocks as dependencies.

I feel that there should be a DRYER way to write this specification, but I cannot figure it out. Does anyone have any ideas?

(I understand that I could write tests for this service as a standard ES6 class, but I get the same script with my components and services that use the angular Http distracting stuff.)

Here is my specification file:

import { TestBed, inject } from '@angular/core/testing'; import { Observable } from 'rxjs/Observable'; import { UserService, RestService } from '../index'; import { User } from '../../../models/index'; let getUserSpy = jasmine.createSpy('getUser'); let upsertUserSpy = jasmine.createSpy('upsertUser'); // NOTE that initally, the MockRestService throws errors for all responses class MockRestService { getUser = getUserSpy.and.returnValue(Observable.throw('no thanks')); upsertUser = upsertUserSpy.and.returnValue(Observable.throw('no thanks')); } describe('User service - ', () => { let service; /** * First TestBed configuration */ beforeEach(() => { TestBed.configureTestingModule({ providers: [ UserService, { provide: RestService, useClass: MockRestService, } ] }); }); beforeEach(inject([UserService], (user: UserService) => { service = user; })); /* ... tests ... */ describe('getUser/ upsertUser succeeds with INVALID user - ', () => { /** * Altering mock */ class MockRestService { getUser = getUserSpy.and.returnValue(Observable.of({json: () => { return {name: 'dave'}; }})); upsertUser = upsertUserSpy.and.returnValue(Observable.of({json: () => {}})); } /** * Reset and reconfigure TestBed. Lots of repetition! */ beforeEach(() => { TestBed.resetTestingModule(); }); beforeEach(() => { TestBed.configureTestingModule({ providers: [ UserService, { provide: RestService, useClass: MockRestService, } ] }); }); beforeEach(inject([UserService], (user: UserService) => { service = user; })); /* ... tests ... */ }); describe('getUser/upsertUser succeeds with valid user', () => { const validResponse = { json: () => { return { firstName: 'dave', lastName: 'jones', email: ' dave@gmail.com ' }; } }; /** * Altering mock */ class MockRestService { getUser = getUserSpy.and.returnValue(Observable.of(validResponse)); upsertUser = upsertUserSpy.and.returnValue(Observable.of(validResponse)); } /** * Reset and reconfigure testbed. Lots of repetition! */ beforeEach(() => { TestBed.resetTestingModule(); }); beforeEach(() => { TestBed.configureTestingModule({ providers: [ UserService, { provide: RestService, useClass: MockRestService, } ] }); }); beforeEach(inject([UserService], (user: UserService) => { service = user; })); /* ... tests ... */ }); }); 
+5
source share
1 answer

It may be some variation.

 function setupUserTestbed() { beforeEach(() => { TestBed.configureTestingModule({...}); }); afterEach(() => { TestBed.resetTestingModule(); }); } ... setupUserTestbed(); ... setupUserTestbed(); 

But the purpose of the describe blocks (in addition to grouping the specifications in the test report) is to arrange the before* and after* blocks so that they are the most efficient.

If the top-level describe block has a beforeEach block, you can be sure that it affects the specifications in the nested describe blocks. If the describe blocks are siblings, the general behavior should be moved to the upper level describe . If there is no top level describe for sibling describe blocks, it must be created.

The published top-level code describe('User service - ', () => { ... }) already has beforeEach blocks with TestBed.configureTestingModule , TestBed.resetTestingModule (this should be done in afterEach ) and inject . There is no need to duplicate them in nested describe blocks.

The recipe for the MockRestService class is the same as for any layout that alternates between specifications. This should be a let / var variable:

 describe(... let MockRestService = class MockRestService { ... }; beforeEach(() => { Testbed... }); describe(... MockRestService = class MockRestService { ... }; beforeEach(inject(...)); 

There may be many variations of this pattern. The class itself may be constant, but the getUser and upsertUser may alternate:

 let getUserSpy; let upsertUserSpy; class MockRestService { getUser = getUserSpy; ... } describe(... beforeEach(() => { Testbed... }); beforeEach(() => { getUserSpy = jasmine.createSpy().and.returnValue(...); ... }); describe(... beforeEach(() => { getUserSpy = jasmine.createSpy().and.returnValue(...); ... }); beforeEach(inject(...)); 

This also solves an important problem, because spies must be fresh in every specification, i.e. be defined in beforeEach . getUserSpy and upsertUserSpy can be reassigned after the Testbed configuration, but before inject (an instance of the MockRestService class can be created MockRestService ).

+3
source

Source: https://habr.com/ru/post/1262569/


All Articles