Skip to main content

Integration

Alright all the coding is complete! Now it is time to test our API from the front door. To achieve this we are going to use a http client calling our actual API gateway deployed in the cloud.

Make sure you have all the code changes you made deployed:

pnpm run deploy

And start up the integration tests:

pnpm run test:integration

Just like when we were testing the secondary adaptors, we want to pull in some parameters from our AWS deployment to interact with them. The test:integration script will bind the Confg parameters from our stack to our test harnesses through the magic of AWS SSM parameter store.

Invalid Payload (400)

When the user provides an invalid body to the api we expect it to return a 400 Bad Request. This behaviour is implemented in the configuration for the API Gateway making it a prime candidate for integration testing, as we cannot validate this behaviour running the code locally.

Unskip the next test case. Remove the .todo: it.todo(...) --> it(...):

backend/tests/integration/api-gw-apply-for-loan.spec.ts
// ...
describe.concurrent('api-aw-apply-for-loan', () => {
const apiClient = openApiFetch<paths>({
baseUrl: baseUrl,
});
it.todo(
'responds with a 400 Bad Request given the user does not provide the required request body',
async ({ expect }) => {
await expect(
apiClient.POST('/loan', {
body: {} as unknown as ApplyForLoanRequestBody,
})
).resolves.toEqual({
error: {
message: 'Invalid request body',
},
response: expect.objectContaining({
status: 400,
statusText: 'Bad Request',
}),
});
}
);
// ...
});

Borrower Does Not Exist (400)

Similar to our previous API endpoints we want to return a 400 with the message of "borrower with the provided email does not exist". This behaviour was implemented by catching the exception in the primary adaptor.

Unskip the next test case. Remove the .todo: it.todo(...) --> it(...):

backend/tests/integration/api-gw-apply-for-loan.spec.ts
// ...
describe.concurrent('api-aw-apply-for-loan', () => {
const apiClient = openApiFetch<paths>({
baseUrl: baseUrl,
});
// ...
it.todo(
'response with a 400 Bad Request given the user provides the required request body but the borrower profile does not exist',
async ({ expect }) => {
const borrowerEmail = `loan-application+${randomUUID()}@example.com`;
await expect(
apiClient.POST('/loan', {
body: {
borrowerEmail: borrowerEmail,
grossAnnualIncome: 100_000,
employmentStatus: 'FULL_TIME',
monthlyExpenses: 1000,
},
})
).resolves.toEqual({
error: {
message: 'Borrower with the provided email does not exist',
},
response: expect.objectContaining({
status: 400,
statusText: 'Bad Request',
}),
});
}
);
// ...
});

New Loan Application Created (201)

Finally we want to test the successful case. To do this we create a borrower using another api and then POST the loan api with a borrower with the same email address

Unskip the next test case. Remove the .todo: it.todo(...) --> it(...):

backend/tests/integration/api-gw-apply-for-loan.spec.ts
// ...
describe.concurrent('api-aw-apply-for-loan', () => {
const apiClient = openApiFetch<paths>({
baseUrl: baseUrl,
});
// ...
it.todo(
'responds with a 201 Submitted with the loan application status given the user provides the required request body when the borrower profile exists',
async ({ expect }) => {
const borrowerEmail = `loan-application+${randomUUID()}@example.com`;
await apiClient.POST('/borrower', {
body: {
email: borrowerEmail,
name: 'John Doe',
creditScore: 800,
dob: '1999-01-01',
},
});
await expect(
apiClient.POST('/loan', {
body: {
borrowerEmail: borrowerEmail,
grossAnnualIncome: 100_000,
employmentStatus: 'FULL_TIME',
monthlyExpenses: 1000,
},
})
).resolves.toEqual({
data: {
loanApplicationStatus: 'APPROVED',
},
response: expect.objectContaining({
status: 201,
statusText: 'Created',
}),
});
}
);
// ...
});

Smoke Test

Now we have finished all our automated integration tests have a look in X-Ray and in the Dynamo table to confirm the application is working. This is similar to when we were exploring the /borrower & /borrowingCapcity endpoints.