diff --git a/docs/site/Calling-other-APIs-and-Web-Services.md b/docs/site/Calling-other-APIs-and-Web-Services.md index 28f2e34e98c8..5531192f19ae 100644 --- a/docs/site/Calling-other-APIs-and-Web-Services.md +++ b/docs/site/Calling-other-APIs-and-Web-Services.md @@ -134,3 +134,50 @@ const myController = await context.get( '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 { + constructor( + @inject('datasources.geoService') + protected datasource: juggler.DataSource = new GeocoderDataSource(), + ) {} + + value(): GeocoderService { + 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. diff --git a/docs/site/Testing-your-application.md b/docs/site/Testing-your-application.md index 2721dde5ae24..e2fd17e5ffd7 100644 --- a/docs/site/Testing-your-application.md +++ b/docs/site/Testing-your-application.md @@ -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 @@ -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