Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Fastify Meets WireMock: External Service Mocking
Claranet profile imageMassimo Biagioli
Massimo Biagioli forClaranet

Posted on

Fastify Meets WireMock: External Service Mocking

WireMock Wonders: Boosting Fastify Testability

This article reveals how to integrateWireMock into Fastify with ease, enabling developers to effortlessly generate mock responses for external services. Join us as we explore the straightforward process of seamlessly integrating and optimizing Fastify applications using WireMock for enhanced testing capabilities.

A Use Case: Integration with an External Service

In this scenario, we'll illustrate the integration with an external service. The service in focus simulates calls to a generic "devices" management system. Our objective is to retrieve the list of devices, read a device by its ID, and create a new one. Let's delve into the practical application of WireMock within Fastify for this specific use case.

Fastify App

We kick off by creating a simple Fastify application in TypeScript, utilizing the fastify-cli.

src/app.ts

exportinterfaceAppOptionsextendsFastifyServerOptions,Partial<AutoloadPluginOptions>{}constoptions:AppOptions={}constFastifyEnvOpts={dotenv:true,schema:{type:'object',required:['DEVICE_SERVER_URL'],properties:{DEVICE_SERVER_URL:{type:'string'}}}}constapp:FastifyPluginAsync<AppOptions>=async(fastify,opts):Promise<void>=>{voidfastify.register(FastifyEnv,FastifyEnvOpts)voidfastify.register(AutoLoad,{dir:join(__dirname,'plugins'),options:opts})voidfastify.register(AutoLoad,{dir:join(__dirname,'routes'),options:opts})}exportdefaultappexport{app,options}
Enter fullscreen modeExit fullscreen mode

Building a Service to Interact with the External System

Now, let's craft a service that interacts with the external system. In this section, we'll guide you through the process of creating a Fastify service that communicates with the simulated external 'devices' management system. This hands-on approach will showcase the seamless integration of our Fastify application with WireMock for effective testing and interaction with external services.

src/plugins/deviceService.ts

// ... importsexportdefaultfp(async(fastify,opts)=>{constSERVER_BASE_URL=`${fastify.config.DEVICE_SERVER_URL}/api/v1/device`constdeviceService={getDevices:async():Promise<Device[]>=>{constresponse=awaitfetch(SERVER_BASE_URL)returnawaitresponse.json()asDevice[]},getDeviceById:async(id:string):Promise<Device|null>=>{constresponse=awaitfetch(`${SERVER_BASE_URL}/${id}`)if(response.status===404){returnnull}returnawaitresponse.json()asDevice},createDevice:async(device:DeviceRequest):Promise<Device>=>{constresponse=awaitfetch(SERVER_BASE_URL,{method:'POST',body:JSON.stringify(device),headers:{'Content-Type':'application/json'}})returnawait(awaitresponse.json()asPromise<Device>)}}fastify.decorate('deviceService',deviceService)})
Enter fullscreen modeExit fullscreen mode

src/model/device.ts

exportinterfaceDeviceRequest{name:stringtype:stringaddress:string}exporttypeDevice=DeviceRequest&{id:string}
Enter fullscreen modeExit fullscreen mode

Using the deviceService in the Controller

src/routes/device/index.ts

// ...importsconstroute:FastifyPluginAsync=async(fastify,opts):Promise<void>=>{fastify.get<{Reply:Device[]}>('/',asyncfunction(request,reply){returnawaitfastify.deviceService.getDevices()})fastify.get<{Params:GetDeviceByIdParams,Reply:Device|GetDeviceByIdError}>('/:id',asyncfunction(request,reply){constresult=awaitfastify.deviceService.getDeviceById(request.params.id)if(result===null){returnawaitreply.notFound()}returnresult})fastify.post<{Body:DeviceRequest,Reply:Device}>('/',asyncfunction(request,reply){constresult=awaitfastify.deviceService.createDevice(request.body)returnreply.code(201).send(result)})}exportdefaultroute
Enter fullscreen modeExit fullscreen mode

Setting Up WireMock

Let's dive into configuring WireMock for our project. We'll employ Docker to define the WireMock service, simplifying the setup process. By using containers, we ensure a convenient and reproducible environment for running WireMock alongside our Fastify application.

docker-compose.yml

version:'3.9'services:wiremock:image:wiremock/wiremock:3.3.1restart:alwaysports:-'8080:8080'volumes:-./wiremock_data:/home/wiremockentrypoint:["/docker-entrypoint.sh","--global-response-templating","--verbose"]
Enter fullscreen modeExit fullscreen mode

wiremock_data/mappings/get_devices.json

{"request":{"url":"/api/v1/device","method":"GET"},"response":{"status":200,"bodyFileName":"get_devices_response.json","headers":{"Content-Type":"application/json"}}}
Enter fullscreen modeExit fullscreen mode

wiremock_data/__files/get_devices_response.json

[{"id":1,"name":"First Device","family_id":1,"address":"10.0.1.1"},...]
Enter fullscreen modeExit fullscreen mode

Image description

wiremock_data/mappings/get_device_by_id.json

{"request":{"urlPattern":"^/api/v1/device/\\d*","method":"GET"},"response":{"status":200,"bodyFileName":"get_device_by_id_response.json","headers":{"Content-Type":"application/json"}}}
Enter fullscreen modeExit fullscreen mode

wiremock_data/__files/get_device_by_id_response.json

This template utilizes dynamic templating, generating a response tailored to the input parameters. In this instance, the 'id' parameter influences the response, showcasing the flexibility and adaptability achieved through WireMock's templating capabilities.

{"id":{{request.pathSegments.[3]}},"name":"Device {{ request.pathSegments.[3] }}","family_id":1,"address":"10.0.1.{{ request.pathSegments.[3] }}"}
Enter fullscreen modeExit fullscreen mode

Image description

wiremock_data/mappings/get_device_by_id_404.json

{"request":{"urlPattern":"^/api/v1/device/\\d*404$","method":"GET"},"response":{"status":404,"body":""}}
Enter fullscreen modeExit fullscreen mode

The setup is designed to manage scenarios where the 'id' ends with '404'. When a request matches the defined URL pattern and method (GET), WireMock responds with a 404 status code, providing a straightforward mechanism to simulate and test error conditions for our Fastify application.

Image description

wiremock_data/mappings/create_device.json

{"request":{"url":"/api/v1/device","method":"POST","headers":{"Content-Type":{"equalTo":"application/json"}},"bodyPatterns":[{"matchesJsonPath":"$.name"},{"matchesJsonPath":"$.family_id"},{"matchesJsonPath":"$.address"}]},"response":{"status":201,"bodyFileName":"create_device_response.json","headers":{"Content-Type":"application/json"}}}
Enter fullscreen modeExit fullscreen mode

wiremock_data/__files/create_device_response.json

{"id":{{randomValuetype='NUMERIC'length=5}},"name":"{{jsonPath request.body '$.name'}}","family_id":{{jsonPathrequest.body'$.family_id'}},"address":"{{jsonPath request.body '$.address'}}"}
Enter fullscreen modeExit fullscreen mode

We leverage dynamic templating to generate varied responses. The 'id' field is populated with a random numeric value of length 5. The 'name', 'family_id', and 'address' fields are populated based on the corresponding values present in the incoming request body. This dynamic approach allows us to simulate diverse scenarios and handle input data flexibly within our WireMock setup.

Image description

Testing

During the testing phase, we don't need to perform service-level mocks since we are directing our requests to the WireMock URL. By utilizing WireMock as the target URL, we seamlessly integrate our Fastify application with the WireMock service, allowing for realistic simulations and comprehensive testing scenarios without the need for extensive service-level mocking.

test/routes/device.test.ts

...test('get device by id',async(t)=>{constapp=awaitbuild(t)constres=awaitapp.inject({url:'/device/123'})constpayload=JSON.parse(res.payload)assert.equal(res.statusCode,200)assert.deepStrictEqual(payload,{id:123,name:'Device 123',family_id:1,address:'10.0.1.123'})})test('get device by id - 404',async(t)=>{constapp=awaitbuild(t)constres=awaitapp.inject({url:'/device/1404'})constpayload=JSON.parse(res.payload)assert.equal(res.statusCode,404)assert.deepStrictEqual(payload,{"statusCode":404,"error":"Not Found","message":"Not Found"})})...
Enter fullscreen modeExit fullscreen mode

Conclusions

WireMock provides a comprehensive solution for building a test suite towards external services, eliminating the need for patching services at the application level. This approach enables us to conduct real tests, fostering a robust testing environment and ensuring the reliability and effectiveness of our Fastify application in interacting with external services.
The project code is in this GitHub repository:fastify-wiremock-example.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Make Modern Happen

More fromClaranet

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp