I'm writing some tests for a service and I'm altering the response from mock functions to test various cases. At the moment, every time I want to change the response of a mock, I need to reset the TestBed and configure the testing module again, injecting my new Mocks as dependencies.
I feel like there must be a DRYer way to write this spec, but I can't 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 scenario with my components and services that use the Http response mocking stuff from angular.)
Here is my spec 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 ... */
});
});
It could be some variation of
But the purpose of
describe
blocks (besides grouping the specs in test report) is to arrangebefore*
andafter*
blocks in a way they are most efficient.If top-level
describe
block hasbeforeEach
block, you can be sure that it affects the specs in nesteddescribe
blocks. Ifdescribe
blocks are siblings, common behaviour should be moved to top-leveldescribe
. If there's no top-leveldescribe
for siblingdescribe
blocks, it should be created.In posted code top-level
describe('User service - ', () => { ... })
already hasbeforeEach
blocks withTestBed.configureTestingModule
,TestBed.resetTestingModule
(it should be performed inafterEach
) andinject
. There's no need to duplicate them in nesteddescribe
blocks.The recipe for
MockRestService
class is the same as for any mock that alternates between specs. It should be alet
/var
variable:There can be a lot of variations of this pattern. The class itself may be constant, but
getUser
andupsertUser
properties may alternate:This also solves an important issue, because spies should be fresh in each spec, i.e. be defined in
beforeEach
.getUserSpy
andupsertUserSpy
can be re-assigned afterTestbed
configuration but beforeinject
(this is whereMockRestService
class is likely instantiated).