All topics
Testingintermediate

Service Testing and Mocking Dependencies

Explain how to unit test a service in isolation by substituting mock implementations for its dependencies.

Testing a service in isolation means verifying its own logic without accidentally also testing (and depending on) the real behavior of everything it injects — an OrderService that depends on HttpClient and LoggerService should have both replaced with mocks or spies in a unit test, so the test is fast, deterministic, and fails only when OrderService's own logic is actually broken, not when an unrelated dependency changes behavior.

It's like testing a car's dashboard warning-light logic on a test bench using a simulated fuel sensor that you can set to any value on demand, rather than needing to actually drain a real car's fuel tank to verify the low-fuel light triggers correctly.

Key Concepts

1
The standard technique is registering a mock/spy object in TestBed.configureTestingModule's providers array using { provide: RealService, useValue: mockObject } (for a simple stub) or { provide: RealService, useClass: MockServiceClass } (for a more elaborate fake implementation), which overrides the real provider registration purely within that test module's injector, without touching the real service's code at all.
TestBed.configureTestingModuleproviders{ provide: RealService, useValue: mockObject }{ provide: RealService, useClass: MockServiceClass }
2
Jasmine's spyOn(object, 'methodName').and.returnValue(...) (or .and.callThrough(), .and.callFake(...)) is the common way to replace individual methods on an already-instantiated object with a test double, letting you assert both on what the spy returned to the code under test and on how the spy itself was called (expect(spy).toHaveBeenCalledWith(...)) — this is often simpler than building a full separate mock class for services with only one or two methods actually relevant to a given test.
spyOn(object, 'methodName').and.returnValue(...).and.callThrough().and.callFake(...)expect(spy).toHaveBeenCalledWith(...)
3
A thorough interview answer distinguishes unit-level service tests (fast, isolated, mocked dependencies, testing one service's own logic) from integration-style tests (using real dependencies together to verify they cooperate correctly), and notes that a healthy test suite generally has many more of the former than the latter, precisely because isolated unit tests are faster, more reliable, and pinpoint failures more precisely.