0

I'm trying to set up a test for my service using TestBed. That service has some dependencies of other services. I've created .stub files for those dependencies and exported them. Problem is when I try to use these stubs in the test it still seems like the test methods use the real service and not the test. Here's my setup (the fake names are just for this thread):

describe('ServiceToTest', () => {
    let stubbedService: ServiceDependency1;

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [],
            providers: [
                ServiceToTest,
                { provide: ServiceDependency1, useClass: ServiceDependency1Stub },
                { provide: ServiceDependency2, useClass: ServiceDependency2Stub }
            ]
        });

        stubbedService = TestBed.inject(ServiceDependency1);
    });

    it('description', fakeAsync(() => {
        // Error appears here when trying to use the BehaviorSubject on the mock:
        // Property 'behaviorSubjectTest' does not exist on type                 
       'ServiceDependency1'
        stubbedService.behaviorSubjectTest.next(new Test());
        ... more logic
    }));
});

I understand the error, that's because behaviorSubjectTest property is on the stub and not the real service. But why doesn't it use the stub service when I listed it as a useClass in the providers?

What am I doing wrong? When I see what's available on serviceDependency1Stub I see all the methods and properties on the real service and not the stub. This happened after updating to Angular 9. Earlier we used the .get method and then it worked. Then it was set up like this:

describe('ServiceToTest', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [],
            providers: [
                ServiceToTest,
                { provide: ServiceDependency1, useClass: ServiceDependency1Stub },
                { provide: ServiceDependency2, useClass: ServiceDependency2Stub }
            ]
        });
    });

    it('description', fakeAsync(() => {
        const stubbedService = TestBed.get(ServiceDependency1);
        stubbedService.behaviorSubjectTest.next(new Test());
        ... more logic
    }));
});

And here's the .stub file:

export class ServiceDependency1Stub {
    public behaviorSubjectTest = new BehaviorSubject<Test>(null);
}

I guess the setup from start was wrong, but not exposed before now after changing to .inject? I've looked at the documentation but I can't find any good examples on testing service with dependencies. If anyone could help me and point me in the right direction I would appreciate that. Thanks.

odke
  • 59
  • 1
  • 1
  • 3
  • Is the error coming at runtime, or from TypeScript? You've typed the stub variable as the dependency itself, so typescript won't give you access to the extra stub properties. – jonrsharpe Apr 07 '20 at 12:48
  • From TypeScript. The error shows up in the editor. If you're referring to the let variable I edited and renamed it now to make it more clear. I don't actually use these names, just replaced the real names to hide the real business logic. – odke Apr 07 '20 at 12:53
  • Yes, `let stubbedService: ServiceDependency1;` - `ServiceDependency1` presumably *doesn't* have the props the stub adds. – jonrsharpe Apr 07 '20 at 12:55
  • So that property would be `behaviorSubjectTest`. The variable name was different in the real service. I've changed it now so the property in the real service and the stubbed service is the same. The only difference now is that the property in the real service is private and the one in the stub is public. Now I get this error: `Property 'behaviorSubjectTest' is private and only accessible within class 'ServiceDependency1'.` So it doesn't seem to use ServiceDependency1Stub – odke Apr 07 '20 at 13:05
  • Why would it? You've **told the compiler** the variable you've assigned the stub to is actually the real thing. You don't need to add props to the thing you're test doubling, private or otherwise, you need to tell the compiler what you actually expect `TestBed.get(ServiceDependency1);` to give you. – jonrsharpe Apr 07 '20 at 13:09
  • Alternatively, create the stub yourself, then `useValue` to put it into the TestBed. Also note that your stub should implement the interface of the thing it's test doubling, to make sure it has all of the right props. – jonrsharpe Apr 07 '20 at 13:15
  • I thought when you provide the stub in the providers like so `{ provide: ServiceDependency1, useClass: ServiceDependency1Stub }` That it will use the stub class and not the real service. – odke Apr 07 '20 at 13:19
  • That's one way to do it, yes. You give the DI the class, then it instantiates it for you. It *is* using the stub service, that's not the problem. The problem is you're trying to access the stub's props on a variable **you said wasn't the stub**. – jonrsharpe Apr 07 '20 at 13:20
  • Not sure I get it. So I should change `stubbedService = TestBed.inject(ServiceDependency1);` to `stubbedService = TestBed.inject(ServiceDependency1Stub)`? – odke Apr 07 '20 at 13:25
  • That will probably tell you it can't resolve the dependency. You need to change the type **of the `stubbedService` variable**. – jonrsharpe Apr 07 '20 at 13:27

0 Answers0