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: '[email protected]'
};
}
};
/**
* 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
function setupUserTestbed() {
beforeEach(() => {
TestBed.configureTestingModule({...});
});
afterEach(() => {
TestBed.resetTestingModule();
});
}
...
setupUserTestbed();
...
setupUserTestbed();
But the purpose of describe
blocks (besides grouping the specs in test report) is to arrange before*
and after*
blocks in a way they are most efficient.
If top-level describe
block has beforeEach
block, you can be sure that it affects the specs in nested describe
blocks. If describe
blocks are siblings, common behaviour should be moved to top-level describe
. If there's no top-level describe
for sibling describe
blocks, it should be created.
In posted code top-level describe('User service - ', () => { ... })
already has beforeEach
blocks with TestBed.configureTestingModule
, TestBed.resetTestingModule
(it should be performed in afterEach
) and inject
. There's no need to duplicate them in nested describe
blocks.
The recipe for MockRestService
class is the same as for any mock that alternates between specs. It should be a let
/var
variable:
describe(...
let MockRestService = class MockRestService { ... };
beforeEach(() => { Testbed... });
describe(...
MockRestService = class MockRestService { ... };
beforeEach(inject(...));
There can be a lot of variations of this pattern. The class itself may be constant, but getUser
and upsertUser
properties 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 issue, because spies should be fresh in each spec, i.e. be defined in beforeEach
. getUserSpy
and upsertUserSpy
can be re-assigned after Testbed
configuration but before inject
(this is where MockRestService
class is likely instantiated).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With