Home > Enterprise >  How to mock observable from service in marble testing
How to mock observable from service in marble testing

Time:02-08

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 ?

CodePudding user response:

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

  •  Tags:  
  • Related