I want to test an Angular service which contains my logic. I will simplify my case in order to make it straightforward :
I have logic$ which is what I want to test, it is bound to data$, another observable
@Injectable({
providedIn: 'root',
})
export class MyService {
readonly data = new BehaviorSubject<string>('');
readonly data$ = this.data.asObservable();
readonly logic$ = this.data$.pipe(
map((logic: string) => `Mighty ${logic}`)
)
constructor() {}
}
I want to be able to mock the data in my test and see if my logic is behaving as intended
describe('MyService', () => {
let myService: MyService;
const testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
beforeEach(() => {
myService = new MyService();
myService.data$ = of("foo", "bar");
TestBed.configureTestingModule({
providers: [{ provide: MyService, useValue: myService }]
});
});
it('should be created', inject([MyService], (service: MyService) => {
expect(service).toBeTruthy();
}));
it('should be toto lala', inject([MyService], (service: MyService) => {
testScheduler.run(helpers => {
const { expectObservable, cold } = helpers;
const expect$ = "ab";
expectObservable(service.logic$).toBe(expect$, {
a: "Mighty foo",
b: "Mighty bar",
})
});
}));
});
I'm getting the following error :
Chrome Headless 97.0.4692.99 (Windows 10) MyService should be toto lala FAILED
Expected $.length = 1 to equal 2.
Expected $[0].notification.value = 'Mighty ' to equal 'Mighty foo'.
Expected $[1] = undefined to equal Object({ frame: 1, notification: Notification({ kind: 'N', value: 'Mighty bar', error: undefined, hasValue: true }) }).
Error: Expected $.length = 1 to equal 2.
Expected $[0].notification.value = 'Mighty ' to equal 'Mighty foo'.
Expected $[1] = undefined to equal Object({ frame: 1, notification: Notification({ kind: 'N', value: 'Mighty bar', error: undefined, hasValue: true }) }).
at <Jasmine>
at TestScheduler.assertDeepEqual (projects/ui-affaire-client/src/lib/components/my-component/my-component.service.spec.ts:11:20)
at node_modules/rxjs/_esm2015/internal/testing/TestScheduler.js:110:1
at <Jasmine>
Chrome Headless 97.0.4692.99 (Windows 10): Executed 2 of 3 (1 FAILED) (0 secs / 0.028 secs)
Chrome Headless 97.0.4692.99 (Windows 10) MyService should be toto lala FAILED
Expected $.length = 1 to equal 2.
Expected $[0].notification.value = 'Mighty ' to equal 'Mighty foo'.
Expected $[1] = undefined to equal Object({ frame: 1, notification: Notification({ kind: 'N', value: 'Mighty bar', error: undefined, hasValue: true }) }).
Error: Expected $.length = 1 to equal 2.
Expected $[0].notification.value = 'Mighty ' to equal 'Mighty foo'.
Expected $[1] = undefined to equal Object({ frame: 1, notification: Notification({ kind: 'N', value: 'Mighty bar', error: undefined, hasValue: true }) }).
at <Jasmine>
at TestScheduler.assertDeepEqual (projects/ui-affaire-client/src/lib/components/my-component/my-component.service.spec.ts:11:20)
at node_modules/rxjs/_esm2015/internal/testing/TestScheduler.js:110:1
Chrome 97.0.4692.99 (Windows 10) MyService should be toto lala FAILED
Expected $.length = 1 to equal 2.
Expected $[0].notification.value = 'Mighty ' to equal 'Mighty foo'.
Expected $[1] = undefined to equal Object({ frame: 1, notification: Notification({ kind: 'N', value: 'Mighty bar', error: undefined, hasValue: true }) }).
Error: Expected $.length = 1 to equal 2.
Expected $[0].notification.value = 'Mighty ' to equal 'Mighty foo'.
Expected $[1] = undefined to equal Object({ frame: 1, notification: Notification({ kind: 'N', value: 'Mighty bar', error: undefined, hasValue: true }) }).
at <Jasmine>
at TestScheduler.assertDeepEqual (projects/ui-affaire-client/src/lib/components/my-component/my-component.service.spec.ts:11:20)
at node_modules/rxjs/_esm2015/internal/testing/TestScheduler.js:110:1
at <Jasmine>
Chrome Headless 97.0.4692.99 (Windows 10): Executed 2 of 3 (1 FAILED) (skipped 1) (0.097 secs / 0.028 secs)
Chrome 97.0.4692.99 (Windows 10): Executed 1 of 3 (1 FAILED) (0 secs / 0.019 secs)
Chrome 97.0.4692.99 (Windows 10) MyService should be toto lala FAILED
Expected $.length = 1 to equal 2.
Expected $[0].notification.value = 'Mighty ' to equal 'Mighty foo'.
Expected $[1] = undefined to equal Object({ frame: 1, notification: Notification({ kind: 'N', value: 'Mighty bar', error: undefined, hasValue: true }) }).
Error: Expected $.length = 1 to equal 2.
Expected $[0].notification.value = 'Mighty ' to equal 'Mighty foo'.
Expected $[1] = undefined to equal Object({ frame: 1, notification: Notification({ kind: 'N', value: 'Mighty bar', error: undefined, hasValue: true }) }).
at <Jasmine>
at TestScheduler.assertDeepEqual (projects/ui-affaire-client/src/lib/components/my-component/my-component.service.spec.ts:11:20)
Chrome Headless 97.0.4692.99 (Windows 10): Executed 2 of 3 (1 FAILED) (skipped 1) (0.097 secs / 0.028 secs)
Chrome 97.0.4692.99 (Windows 10): Executed 2 of 3 (1 FAILED) (skipped 1) (0.099 secs / 0.026 secs)
TOTAL: 2 FAILED, 2 SUCCESS
TOTAL: 2 FAILED, 2 SUCCESS
For what I can understand, visibly my mock is not working. logic$ has a length of 1 and the data emits only '' (which is emitted by the behaviorSubject) My guess is that my mock is done after the creation of the service and therefore, the "old" observables are still used and not replaced.
Is there a way to properly replace observables to test logic in a service ?
When you create a new instance of MyService
, the property logic$
gets assigned using the initial value of data$
.
Even if you change the value of data$
afterwards, the logic$
property wont be re-evaluated, so it will keep having the same initial value (using the BehaviorSubject as source).
To test logic$
using marbles, you could do something like this:
it('should be toto lala', inject([MyService], (service: MyService) => {
testScheduler.run((helpers) => {
const { expectObservable, cold } = helpers;
const sourceMarbles = '-1-2';
const expectedMarbles = 'ia-b';
// create cold obs as source
const source = cold(sourceMarbles, { 1: 'foo', 2: 'bar' });
// subscribe BehaviourSubject to source to relay emitted values
source.subscribe(service.data);
// test the output of logic$
expectObservable(service.logic$).toBe(expectedMarbles, {
i: 'Mighty ', // <- this accounts for the initial value of the Behavior
a: 'Mighty foo',
b: 'Mighty bar',
});
});
}));
cheers