I have a service that subscribes to an observable field of a dependency in the constructor. I am trying to write unit tests for this service, and in doing so, am trying to ignore the subscription to that observable. In other words, for some of these tests, I never want to emit a value, as I'm testing an unrelated function. How can I create a mock/spy of my service that simply does nothing with that observable?
Service
export class MyService {
constructor(private authService: AuthService) {
this.authService.configure();
this.authService.events.subscribe(async (event) => {
await this.authService.refreshToken();
});
}
AuthService dependency
This is a snippet of the 3rd party service that my service depends on.
export declare class AuthService implements OnDestroy {
events: Observable<Event>;
// ... other fields/functions
}
Spec
describe('MyService', () => {
let authServiceSpy: jasmine.SpyObj<AuthService>;
beforeEach(() => {
authServiceSpy = jasmine.createSpyObj<AuthService>(
['configure', 'refreshToken'],
{
get events() { return defer(() => Promise.resolve(new Event('some_event'))); }
}
);
TestBed.configureTestingModule({});
});
it('should create service and configure authService', () => {
const myService = new myService(authServiceSpy);
expect(myService).toBeTruthy();
expect(authServiceSpy.configure).toHaveBeenCalledOnce();
});
});
Whenever I run this test, the expectation is met but it also runs the callback in the observable subscription, and I haven't mocked the refreshToken
call (and don't want to have to).
CodePudding user response:
You need to inject your mock service, like this:
let authService: AuthService;
...
TestBed.configureTestingModule({
providers: [
{ provide: AuthService, useValue: authServiceSpy}
]
});
authService = TestBed.inject(AuthService);
Then you can also spy on the event subscribe function, so that your spy will be called, not the original function.
spyOn(authService.events, 'subscribe');
You can also just use of()
for the mock value of events, instead of the defer call you have there.
CodePudding user response:
Ok, after much head-banging, I figured out what I needed to do. If I create a Subject
and return it from my mocked events
field, I can control the value emits. Without calling next()
on my Subject
, no value is emitted. It also allows me to test the callback of the subscribe function in a separate test.
describe('MyService', () => {
let authServiceEventsSubject: Subject<Event>;
let authServiceSpy: jasmine.SpyObj<AuthService>;
beforeEach(() => {
authServiceSpy = jasmine.createSpyObj<AuthService>(
['configure', 'refreshToken'],
{
get events() { return authServiceEventsSubject; }
}
);
TestBed.configureTestingModule({});
});
// TEST PASSES
it('should create service and configure authService', () => {
const myService = new myService(authServiceSpy);
expect(myService).toBeTruthy();
expect(authServiceSpy.configure).toHaveBeenCalledOnce();
});
// TEST PASSES
it('should refresh the access token on "token_expires" event', () => {
const myService = new MyService(environmentConfigSpy, oAuthServiceSpy);
// Emit a 'token_expires' event
authServiceEventsSubject.next(new Event('token_expires'));
expect(authServiceSpy.refreshToken).toHaveBeenCalledTimes(1);
})
});