decoupling to accelerate web api testing
introduction
when i entered any url into my browser, a http call will be made to a server to request for that particular resource. The request will be based on the http verb and the resource is the path which is part of the url.
From user point of view i will see only url which represents a machine/server at other side which will serve my request. Of course in reality, there are many things happen behind the scene. This bring us to the old concept and a very fundamental in programming/architecture: interface.
ada apa dengan interface
we always interact with interface and take it for granted. Nowdays everybody carries a smartphone. Where do you plug ur charger? there is a interface for that in the form of :
The above is just simplification of how interface make our life easier. It abstract away all the complexity. As long i can get the 'juice' flowing into my smartphone, i dont care what magical things happen behind the scene. The interface provide a contract between the consumer ( my smartphone charger) and provider ( the power company ). So when you use browser, know that you browser is api consumer and some machine inside your browser url is the api provider.
its all about testing
in the world of connecting devices. How do you scope what need to be test or not? it is super hard question but someone need to address it. What are the boundaries of testing? what test strategy should we adopt? Do we test all the component involve ?
most popular choice and by default: lets do end to end testing.
unpopular choice and perhaps, radical: contract testing
In my mind, if i talk about web api testing my default choice would be falls to contract testing.
painful scenario of api provider and api consumer
imagine a fictional scenario:
bring me...pain
lob have many intiative happening and they also have many partner line up.
partner have many initiative happening and they have many other organizations to work with in order to provide marvellous digital offering.
coordination between monolith org and the partner need to be set to minimum. A side by side coordination just too complex top it with expensive as well.
the dedicated team have to juggle with many lob and many partner.
imagine when we apply end to end testing ?
decoupling to the rescue
instead of doing end to end like this
partner -> system which we dedicated team handle -> lob
we go with
system which we dedicated team handle -> lob then partner -> system which we dedicated team handle
important aspect to understand is the seggregation between api provider and api consumer. We can move the perspective of our test scope so the assignment of api provider and api consumer change but it still adhere to the web api contract.
talk is cheap, show me the running program
I will be imagining myself as one of the member of dedicated team ( between partner and lob, imagine malcom in the middle)
Recommended by LinkedIn
Customer can go to our partner digital platform. They can choose our monolith organization financial product ( loan rumah ) then customer can submit from their digital platform. Customer will be notify when the loan status change.
important pre-requisite
we are about test the interface and not the functionality lies behind the interface. To make this more palatable
abide to the contract
openapi: 3.0.3
info:
title: Mortgage AIP Loan API
version: 1.0.0
description: API for submitting customer information for approval in principle for home loan application.
paths:
/mortgage/aip/loan:
post:
summary: Submit customer information for AIP home loan application
requestBody:
description: Loan request including customer information and callback URL
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoanRequest'
responses:
'202':
description: Accepted
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: "Loan application received, processing initiated."
referenceId:
type: string
example: "12345"
callbacks:
loanStatusCallback:
'{$request.body#/callback}/mortgage/aip/loan/{$response.body#/referenceId}':
post:
summary: Callback for loan application status
requestBody:
description: Loan application status update
required: true
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: [approved, rejected, pending]
message:
type: string
responses:
'200':
description: Acknowledged
components:
schemas:
LoanRequest:
type: object
properties:
customerInfo:
$ref: '#/components/schemas/CustomerInfo'
callback:
type: string
format: uri
pattern: '^https?://.+'
required:
- customerInfo
- callback
CustomerInfo:
type: object
properties:
customerId:
type: string
name:
type: string
income:
type: number
loanAmount:
type: number
propertyValue:
type: number
required:
- customerId
- name
- income
- loanAmount
- propertyValue
this is openapi. Standard introduce to define web api. But normally this will be always be confuse with swagger.
make it replayable and configurable
The most straighforward thinking is by embedding the response in the application code. But you can guess the high cost of maintaining it over time. Its not wrong, improvement comes from maturity and maturity require time plus effort in learning implementation.
I will take different approach.
define our request response inside a configuration file
{
"request": {
"method": "POST",
"url": "/mortgage/aip/loan",
"bodyPatterns": [
{
"matchesJsonPath": "$[?(@.customerInfo.customerId)]"
},
{
"matchesJsonPath": "$[?(@.customerInfo.name)]"
},
{
"matchesJsonPath": "$[?(@.customerInfo.income)]"
},
{
"matchesJsonPath": "$[?(@.customerInfo.loanAmount)]"
},
{
"matchesJsonPath": "$[?(@.customerInfo.propertyValue)]"
},
{
"matchesJsonPath": "$[?(@.callback)]"
}
]
},
"response": {
"status": 202,
"jsonBody": {
"message": "Loan application received, processing initiated.",
"referenceId": "12345"
},
"headers": {
"Content-Type": "application/json"
}
},
"serveEventListeners": [
{
"name": "webhook",
"parameters": {
"method": "POST",
"url": "http://localhost:8080/api/callback/mortgage/aip/loan/12345",
"headers": {
"Content-Type": "application/json"
},
"body": {
"status": "approved",
"message": "Your loan application has been approved."
},
"fixedDelayMilliseconds": 1000
}
}
]
}
As you can see we will map request to a response. Nothing fancy. This configuration fill will load by an application to serve request with its canned response.
Interesting!
Teach me sifu