Google Cloud Functions: Testing (Part 1)

Arshinskiy Mike
5 min readMay 17, 2020

Best practices for keeping your functions in check

The joy of “Hello World!”

The happiest moment in the development of any application is that first “Hello world” you managed to print. I am pretty certain that the popularity of some technologies and programming languages, in particular, is inversely correlated to the amount of time it takes to get to that “hello world!”.

Cloud functions are not an exception; they allow you to be very fast by cutting the time it takes to set up:

  • Infrastructure. All containerization setup is done for you. Also, provisioning and scaling is taken care of
  • Framework. All framework boilerplate, e.g., Express or Flask, is hidden away from you.

So you get your dose of nerd dopamine much faster, but then you suddenly realize that the same rules apply. Yes, those boring rules — every code must be tested and automatically deployed if you want to work with it for a time period longer than one weekend. So how do we do it with cloud functions?

Types of tests

Google provides a pretty neat explanation of how they see different types of tests. But when you start reading the code, you just ask yourself, is it not just the same thing? So let me give an easy (not an RTFM) explanation:

  • Unit tests are useful when you want to see if your function does what it is supposed to in isolation from other systems and components. To ignore other systems, you will have to do a lot of stubbing and mocking (sweat and short explanation of differences)
  • Integration tests are used when you start dropping those mocks and stubs. You run the code and see it interacts with your other components and external systems as you expect
  • System tests are applied when you deploy your code and see how it plays with all other components in place. A big difference with the integration tests is that it is not your computer anymore

Some academics might disagree with my definitions. There is a fair amount of art you need to master to do testing efficiently. For example, in integration tests, you might want to relax some stubs, but not all, or deploy them already to some staging environment. But roll with me on this one.

Setup

If you are testing your functions through UI on GCP console, just stop. Ok? Google provides an excellent framework to run cloud functions locally — functions-framework. You must try to run things locally before pushing them anywhere.

TLDR; To avoid all the setup steps, you can just clone or fork my boilerplate repository for GCP functions. Work in progress, looking forward to your pull request.

After you initialized your project, install functions-framework:

npm install @google-cloud/functions-framework --save-dev

Add your first functions to index.js. In our example, we have two basic function types — HTTP and background function. They both do the same thing — get the name and print, e.g., ‘Hello Mike’ or ‘Hello World’, when the parameter is not specified. HTTP function replies via HTTP response and background function write it back to a PubSub topic.

Ignore the getDataFromEvent method; for now, it will become apparent later why it is needed.

In your package.json file, you can now specify run-script commands like this:

"scripts": {"background-function": "functions-framework --target=backgroundFunction --signature-type=event --port 8080","http-function": "functions-framework --target=httpFunction --signature-type=http --port 8080"}

One last thing, since you want to test things locally, you want to have PubSub locally. You say impossible? GCloud CLI makes it possible. The following two commands will start the emulator and initialize environment variables. From that point on, every Google Cloud library will talk to your local instance of PubSub.

gcloud beta emulators pubsub start --project=test
$(gcloud beta emulators pubsub env-init)

For tests, we will be using the Jest testing framework. You are more than welcome to look into Mocha.

Unit tests

HTTP Functions

For this type of functions, unit testing is pretty straightforward:

  1. Input: Express Request
  2. Output: Express Response
  3. Side effects: Mock them

I would strongly recommend using the jest-express library for consistency. A happy path unit test for the code above could look like this:

We try to pass GUID as a name, so we can catch it later. The following checks are performed: a) was the send function called at all, i.e. did it send a response, b) was it called with the GUID we passed. You can also add a test to verify that it returned 200. For not happy path testing, checking HTTP response code becomes more important

Background functions

This type is much more challenging to test, because

  1. Inputs are not clearly defined. There are many types of background functions ranging from activation by PubSub to Firebase triggers. Each one of them affects the event parameter. There is another bug, which causes a disparity between what you see with GCP PubSub and your local PubSub.
  2. A lot of side effects. By nature of background functions, effects of it are not returned, but first persisted somewhere else, e.g., a database or a message queue. You will have to work a lot more with mocking to capture those effects.

Jest provides the mechanism of manual mocks. You can put a folder under __mocks__ to match the library you want to mock, and it will do the rest for you. In our case folder structure would be:

__mocks__
|--@google-cloud
|----pubsub.js

To test the example above, the mock is super slim. We just need one function to test so we will mock just that:

The unit test itself is a bit more involved because we have to feed the entire request and also encode it along the way, but that way, we do not have to call any service:

What’s next?

Before I started writing this article, I wanted to cover all types of tests at once. Still, while writing, I have realized that it gets more nuanced the deeper we go in testing, especially around integration tests, so I decided to put it into a separate article with more detailed explanations. Stay tuned!

--

--