Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions docs/site/Calling-other-APIs-and-Web-Services.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,50 @@ const myController = await context.get<MyController>(
'controllers.MyController',
);
```

### Make service proxies easier to test

While `@serviceProxy` decorator makes it easy to use service proxies in
controllers, it makes it difficult to write integration tests to verify that
service proxies are correctly configured/implemented in respect to the most
recent API provided by the backend service implementation. To make service
proxies easy to test, we recommend to write a
[Provider](./Creating-components.md#providers) class that will allow both
controllers and integration tests to access the same service proxy
implementation.

```ts
import {getService, juggler} from '@loopback/service-proxy';
import {inject, Provider} from '@loopback/core';
import {GeocoderDataSource} from '../datasources/geocoder.datasource';

export class GeoServiceProvider implements Provider<GeoService> {
constructor(
@inject('datasources.geoService')
protected datasource: juggler.DataSource = new GeocoderDataSource(),
) {}

value(): GeocoderService {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: is getService a function provided by us or user defines it in the provider?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! getService is coming from @loopback/service-proxy package, see http://apidocs.loopback.io/@loopback%2fdocs/service-proxy.html#9.

I'll add an explicit import to the code snippet.

return getService(this.datasource);
}
}
```

In your application setup, create an explicit binding for the geo service proxy:

```ts
app.bind('services.geo').toProvider(GeoServiceProvider);
```

Finally, modify the controller to receive our new service proxy in the
constructor:

```ts
export class MyController {
@inject('services.geo') private geoService: GeoService;
}
```

Please refer to
[Testing Your Application](./Testing-your-application.md#test-your-services-against-real-backends)
for guidelines on integration testing.
94 changes: 79 additions & 15 deletions docs/site/Testing-your-application.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,19 +497,6 @@ thing covered).
See [Test Sequence customizations](#test-sequence-customizations) in
[Acceptance Testing](#acceptance-end-to-end-testing).

### Unit test your Services

{% include content/tbd.html %}

The initial beta release does not include Services as a first-class feature.

See the following related GitHub issues:

- Define services to represent interactions with REST APIs, SOAP Web Services,
gRPC services, and more:
[#522](https://github.com/strongloop/loopback-next/issues/522)
- Guide: Services [#451](https://github.com/strongloop/loopback-next/issues/451)

## Integration testing

Integration tests are considered "white-box" tests because they use an
Expand Down Expand Up @@ -598,9 +585,86 @@ describe('ProductController (integration)', () => {

### Test your Services against real backends

{% include content/tbd.html %}
When integrating with other services (including our own microservices), it's
important to verify that our client configuration is correct and the client
(service proxy) API is matching the actual service implementation. Ideally,
there should be at least one integration test for each endpoint (operation)
consumed by the application.

The initial beta release does not include Services as a first-class feature.
To write an integration test, we need to:

- Obtain an instance of the tested service proxy. Optionally modify the
connection configuration, for example change the target URL or configure a
caching proxy to speed up tests.
- Execute service proxy methods and verify that expected results were returned
by the backend service.

#### Obtain a Service Proxy instance

In
[Make service proxies easier to test](./Calling-other-APIs-and-Web-Services.md#make-service-proxies-easier-to-test),
we are suggesting to leverage Providers as a tool allowing both the IoC
framework and the tests to access service proxy instances.

In the integration tests, a test helper should be written to obtain an instance
of the service proxy by invoking the provider. This helper should be typically
invoked once before the integration test suite begins.

```ts
import {
GeoService,
GeoServiceProvider,
} from '../../src/services/geo.service.ts';
import {GeoDataSource} from '../../src/datasources/geo.datasource.ts';

describe('GeoService', () => {
let service: GeoService;
before(givenGeoService);

// to be done: add tests here

function givenGeoService() {
const dataSource = new GeoDataSource();
service = new GeoServiceProvider(dataSource).value();
}
});
```

If needed, you can tweak the datasource config before creating the service
instance:

```ts
import {merge} from 'lodash';

const GEO_CODER_CONFIG = require('../src/datasources/geo.datasource.json');

function givenGeoService() {
const config = merge({}, GEO_CODER_CONFIG, {
// your config overrides
});
const dataSource = new GeoDataSource(config);
service = new GeoServiceProvider(dataSource).value();
}
```

#### Test invidivudal service methods

With the service proxy instance available, integration tests can focus on
executing individual methods with the right set of input parameters; and
verifying the outcome of those calls.

```ts
it('resolves an address to a geo point', async () => {
const points = await service.geocode('1 New Orchard Road, Armonk, 10504');

expect(points).to.deepEqual([
{
lat: 41.109653,
lng: -73.72467,
},
]);
});
```

## Acceptance (end-to-end) testing

Expand Down