Serverless & Testing

Omid Eidivandi (XaaXaaX)
6 min readOct 3, 2021

Serverless is modern but enough complicated, there is a need to know roughly the design and its components. it’s hard to find the root of error if your design is not enough rich, if you did not consider the best practices and what ever will keep your design production ready you will be lost in your production in some situations which are vital for your business.

Testing is one of them and it’s a bit complicated to do face it in serverless asynchronous design. when talking about async design always someone think about a 10 or 15 min delay or batches which are cron-ed to be executed every hour, but this is legacy mindset.

In reality, serverless is just noting maintain the servers and let the provider do some staff for you, and async messaging is just decoupling your design and let any part act as a seperated subsystem (it’s clear for you if you ever did the UMLing and surely you will know System diagram)

when we decouple a system we define just the boundaries for that system and will be able to decouple it and refinely test it or agilely deliver it.

Testing:

Testing is one of the concepts in async design which needs an effort and it’s sometimes just out of our mindset and we find some solutions an overkill for just testing, BUT look at two phrases below

Performance is a feature not a choiceAlso Testing is a feature not a choice

with this mindset you will be able to amuse yourself to know better your system and take in consideration some (said overkill) staff.

Their Testable design :

Add alt text

Their Non Testable design:

Add alt text

Test Every Thing:

Every thing fails we are agree with that , so we should test every thing in all following categories:

  1. Unit Testing
  2. Integration Testing
  3. Acceptance Testing
  4. System Testing

Here i will present a simple async design and do a simple E2E test

Add alt text

A simple async design which is decoupled just by a Pub/Sub component( a specific kind of message bus ) and this decoupling blocks having the E2E tests.

But NOOOO, when you modernize your IT you need modernize your mindset, here we need to test our real system and the test needs to simulate or do exactly what one or more consumer do exactly, so we need to test Business Value .

Here, i’ll say

  • Some one calls me for an action
  • I do staff around the request
  • I distribute the results to my consumers who are interested about (or paid for ;) )

So our Test must cover all consumer facing parts.

The Init Part:

so i write the first part of my test

const message = {
"date":"2019-09-18T16:46:00+02:00",
"message":"Hi 'i wana date you",
"FromUser": 2312
"ToUser": 77865
};

So we need to post this message via the http endpoint

const axios = require("axios");

and in my test i add a post call to http endpoint

axios.post(Constants.MY_MESSAGING_SYSTEM_URL, message ,
{ headers :
{ "authorization": MY_SECRET_TOKEN }
}).this((response) => resultId = response.data[0].id))
.catch((error) => console.log(`some error as ${error}`));

So The init part of our system is done we received a 200 http code with an id which identifies our message.

The Validation Part:

This is a bit more complicated but it’s nicely interesting to know how to setup an automated test to act as a real consumer.

Here, pub/sub topic can be an Http Endpoint , a MessageBus, other integrated services or a serverless function.

I ll do simulate the http endpoint as it covers the Subscription part to my Topic and also there is no need to create the new infrastructure but you can go for a aws SQS or Azure Storage Queue scenario also.

const restify = require("restify")
const axios = require("axios")
const ngrok = require("ngrok")
export const RunServer = async (confirmation , messageRecieve ) => {

const url = await ngrok.connect(9898);
var server = restify.createServer()

server.post("/", respond(onConfirmation, onMessage))
server.use(restify.plugins.bodyParser())
server.listen(9898, function() {
console.log(`listening at ${url}`)
})


return {
url,
stop: async () => {
server.close()
await ngrok.kill()
}
}
}

Your respond method looks like a factory to distinguish the confirmation and message receive from the post http call

const respond = (onConfirmation, onMessage) => (req, res, next) => {  if (body.Type === "SubscriptionConfirmation") {    axios.get(body.SubscribeURL).then(() => {
console.log("confirmed SNS subscription")
onConfirmation()
next()
})
} else {
console.log(body.Message)
onMessage(body.Message)
next()
}
}

Now you need add some actions as Subscribing and unsubscribing to the topic.

lets do the subscribe first:

const subscribeToSNS = async (topicArn, url) => {
const resp = await SNS.subscribe({
TopicArn: topicArn,
Protocol: "https",
Endpoint: url,
ReturnSubscriptionArn: true
}).promise()

console.log("subscribed to SNS")
return resp.SubscriptionArn
}

and the unsubscribe as well :

const unsubscribeFromSNS = async (subscriptionArn) => {
await SNS.unsubscribe({
SubscriptionArn: subscriptionArn
}).promise()

console.log("unsubscribed from SNS")
}

At this step we are able to start our simulated server :

const { ReplaySubject, Subject } = require("rxjs")const confirmed = new Subject()
const messages = new ReplaySubject(10)
const topicArn = "aws:arn:*****"
const startPolling = async () => {
const { url, stop: stopServer } = await webserver.start(
() => confirmed.next(),
msg => messages.next(msg))


const subscriptionArn = await subscribeToSNS(topicArn, url)

return new Promise((resolve) => {
confirmed.subscribe(() => resolve({
stop: async () => {
await stopServer()
await unsubscribeFromSNS(subscriptionArn)
}
}))
})
}

we use the rxjs here to do our asynchronous staff you can use any alternative if you like

https://rxjs-dev.firebaseapp.com/guide/overview

now we need to wait for the message to arrive and finally validate our subscription , the last part of our test:

const waitForMessage = (msg) => new Promise((resolve) => {
messages.subscribe(incoming => {
if (incoming.id === msg.id) {
resolve()
}
})
})

We can now change or test to contain the assertion part :

test("Test a date request", async () => {    const message = {
"date":"2019-09-18T16:46:00+02:00",
"message":"Hi 'i wana date you",
"FromUser": 2312
"ToUser": 77865
};
axios.post(Constants.MY_MESSAGING_SYSTEM_URL, message ,
{
headers : {
"authorization": MY_SECRET_TOKEN
}
})
.this((response) => resultId = response.data[0].id))
.catch((error) => console.log(`some error as ${error}`));

await then.messageIsPublishedToSns(message)
})

Add alt text

Yes, we had a E2E test passed in our system with a real behavior and was able to validate our consumer part and totally our business goal or value.

Conclusion :

The Unit tests are nice to have but they are not enough for behavioral assurance tests, also integrated tests don’t cover the whole system, if you test your whole application and not the boundaries you are not doing it well, often the business value is at the boundary line, we lost the customer satisfaction if you are not able to test it.

The success tests are not all you need, you must be able to simulate the different inconvenient situations as when for example a message contains some sensitive data you get it but your processing part will not process that and surely the subscription will never receive that message , so you need to validate this negative case also

A note about tools: as i use nodejs i used jest for demo purpose but for the e2e testing you don’t need use a huge perfect library as jest which do lots of staff behind and became so slow when you have 200 e2e tests, i love the BareTest instead for E2E testing.

--

--

Omid Eidivandi (XaaXaaX)

i'm a technial lead , solution/softwatre architect with more than 20 years of experience in IT industry,, i m a fan of cloud and serverless in practice