# Services

{% embed url="<https://angular.dev/guide/testing/services>" %}
testing-services
{% endembed %}

## Isolated Services

You may test isolated Services that don't depend on any other Services like this.

```typescript
describe("", () => {
    let service: MasterService;

    beforeEach(() => {
        service = new MasterService();
    });

    it("", () => {
        expect(service.getValue()).toBe("");
    });

    it("", () => {
        service.getPromiseValue().then((value) => {
            expect(value).toBe("");
            done();
        });
    });
});
```

## Services with Dependencies

Services often depend on other services that Angular inject into the constructor.

Create and `inject` these dependencies by hand.

{% hint style="danger" %}
These standard testing techniques are great for unit testing services in isolation.

However, you almost always inject services into application classes using Angular dependency injection and you should have tests that reflect that usage pattern.

**Angular testing utilities, like** `TestBed` **make it straightforward to investigate how injected services behave.**

Prefer using [#services-with-testbed](#services-with-testbed "mention").
{% endhint %}

### Injecting with `new`

Only use this way if testing real simple Services.

{% hint style="danger" %}
Prefer injecting with spies as they are usually a better way to mock services.
{% endhint %}

```typescript
describe("", () => {
    let masterService: MasterService;

    it('#getValue should return real value from the real service', () => {
        masterService = new MasterService(new ValueService());
        expect(masterService.getValue()).toBe('real value');
    });
    
    it('#getValue should return faked value from a fakeService', () => {
        masterService = new MasterService(new FakeValueService());
        expect(masterService.getValue()).toBe('faked service value');
    });
    
    it('#getValue should return faked value from a fake object', () => {
        const fake = {getValue: () => 'fake value'};
        masterService = new MasterService(fake as ValueService);
        expect(masterService.getValue()).toBe('fake value');
    });
});
```

### Injecting with spies

```typescript
describe("", () => {
    let masterService: MasterService;

    it('#getValue should return stubbed value from a spy', () => {
        // Create `getValue` spy on an object (in this case method) representing the ValueService
        const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);
        
        // Set the value to return when the `getValue` spy is called.
        const stubValue = 'stub value';
        valueServiceSpy.getValue.and.returnValue(stubValue);
        
        masterService = new MasterService(valueServiceSpy);
        
        expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);
        expect(valueServiceSpy.getValue.calls.count())
            .withContext('spy method was called once')
            .toBe(1);
        expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);
    });
});
```

## Services with `TestBed`

Angular DI handle the discovery and creating of dependant services.

As a service consumer, you don't worry about the order of constructor arguments or how they are created. For this you use `TestBed` testing utility to provide and create services.

### Simple Service test

1. You set the `providers` property with an array of the services that you'll test or mock.
2. Then inject it inside a test by calling `TestBed.inject()` with the service class as the argument.

<pre class="language-typescript"><code class="lang-typescript"><strong>// This is how the `ValueService` testing file should be
</strong><strong>describe("", () => {
</strong>    let service: ValueService;

    beforeEach(() => {
        TestBed.configureTestingModule({ providers: [ValueService] });
    });

    it("", () => {
        service = TestBed.inject(ValueService);
        expect(service.getValue()).toBe("");
    });
});
</code></pre>

### Injecting other services as dependencies

When testing a service with a dependency, provide the mock in the `providers` array.

```typescript
describe("", () => {
    let masterService: MasterService;
    let valueServiceSpy: jasmine.SpyObj<ValueService>;

    beforeEach(() => {
        const spy = jasmine.createSpyObj("ValueService", ["getValue"]);

        TestBed.configureTestingModule({
            // Provide both the service-to-test and it's spy dependency
            providers: [MasterService, { provide: ValueService, useValue: spy }],
        });

        masterService = TestBed.inject(MasterService);
        valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>;
    });

    it('#getValue should return stubbed value from a spy', () => {
        const stubValue = 'stub value';
        valueServiceSpy.getValue.and.returnValue(stubValue);
        
        expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);
        expect(valueServiceSpy.getValue.calls.count())
            .withContext('spy method was called once')
            .toBe(1);
        expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);
    });
});
```

## [Testing `HTTP` Services](https://angular.dev/guide/testing/services#testing-http-services)

Data Services that make HTTP calls to remote servers typically inject and delegate to the Angular `HttpClient` service for XHR calls.

You can test a data service with an injected `HttpClient` spy as you would test any service with a dependency.

### Test with spies

{% hint style="danger" %}
The `subscribe()` method takes a success `next` and fail `error` callback.

Make sure to provide **both** callbacks so that you capture errors.

<mark style="background-color:red;">Neglecting to do so produces an asynchronous uncaught observable error that the test runner will likely attribute to a completly different test.</mark>
{% endhint %}

```typescript
describe("", () => {
    let httpClientSpy: jasmine.SpyObj<HttpClient>;
    let service: ServiceValue;

    beforeEach(() => {
        httpClientSpy = jasmine.createSpyObj("HttpClient", ["get"]);
        service = new ServiceValue(httpClientSpy);
    });

    it("should return expected values (HttpClient called once)", () => {
        const expectedHttpResponse = [{ id: 1, value: "" }];
        httpClientSpy.get.and.returnValue(expectedHttpResponse);
        
        service.getValues().subscribe({
            next: (values) => {
                expect(values).withContext("expected values").toEqual(expectedHttpResponse);
                done();
            },
            error: done.fail,
        });
        
        expect(httpsClientSpy.get.calls.count()).withContext("one call").toBe(1);
    });

    it("should return an error when the server returns a 404", (done: DoneFn) => {
        const errorResponse = new HttpErrorResponse({
            error: "test 404 error",
            status: 404,
            statusText: "Not Found",
        });
        httpClientSpy.get.and.returnValue(asyncError(errorResponse));
        
        service.getValues().subscribe({
            next: (values) => done.fail("expected an error, not values"),
            error: (error) => {
                expect(error.message).toContain("test 404 error");
                done();
            },
        });
    });
});
```

### [Http Testing Library](https://angular.dev/guide/http/testing)

Extended interactions between a data service and the `HttpClient` can be complex and difficult to mock with spies.

#### Test the service

Lets suppose we have a Service that hits an endpoint `url/fruits`.

{% code title="api.service.ts" %}

```typescript
@Injectable({
    providedIn: 'root'
})
export class ApiService {
    private httpClient = inject(HttpClient);
    
    getFruits() {
        return this.httpClient.get<string[]>('url/fruits');
    }
}
```

{% endcode %}

{% code title="api.service.specs.ts" %}

```typescript
describe('ApiService', () => {
    let service: ApiService;
    let httpMock: HttpTestingController;

    beforeEach(() => {
        TestBed.configureTestingModule({
            // In the docs it imports different ones
            providers: [HttpClientTestingModule],
        });
        service = TestBed.inject(ApiService);
        httpMock = TestBed.inject(HttpTestingController);
    });
    
    afterEach(() => {
        // Finally, we can assert that no other requests were made.
        httpMock.verify();
    });
    
    it('should be created', () => {
        expect(service).toBeTruthy();
    });

    it('should fetch data and return list of fruits', () => {
        const expectedFruits = ['grapes', 'pineapple'];
        
        service.getFruits().subscribe((response) => expect(response).toEqual(expectedFruits));
        
        // At this point, the request is pending, and we can assert it was made via the `HttpTestingController`
        const req = httpMock.expectOne('url/fruits');
        
        // We can assert various properties of the request if desired.
        expect(req.request.method).toBe('GET');
        
        // Flushing the request causes it to complete, delivering the result.
        req.flush(expectedFruits);
    });
});
```

{% endcode %}

#### Testing error handling

To test handling of backend errors (when the server returns a non-successful status code), flush requests with an error response that emulates what your backend would return when a request fails.

```typescript
const req = httpMock.expectOne('url/fruits');
req.flush('Failed!', { status: 500, statusText: 'Internal Server Error' });
```

#### Testing Interceptors

[More on the docs.](https://angular.dev/guide/http/testing#testing-an-interceptor)
