Cloud Resume Challenge – Step 11, Tests

Now that I have both the front and back ends of my site working, its time to start on the automation & CI portion of the project. I’ll cover each step of the process in a separate post but here’s a summary of what I want to achieve:

  • When a commit from my development branch (on either the frontend or backend parts of the project) is pushed to GitHub, I want my IAC solution to provision any resources that are required (or else confirm that none are required) in my QA GCP project, the project to be updated to reflect the new changes, everything to be automatically tested and, if the tests pass, I want a to automatically create a pull request to merge the working branch into the main branch.
  • Then I want Terraform to run on my production environment and the deployment plan to be updated in a comment on the pull request.
  • Finally, if the pull request is approved, I want the any new content / resources to be deployed in my production environment, and a final suite of tests run to confirm that everything is working as expected.

Lots to do here but, as it’s the next named step in the challenge, I’ll start with testing.

As my approach here, I’m going to use end-to-end (e2e) testing of the live URLs of my site and API to ensure that the site loads and functions as expected, that in passing the right values to the API everything works as expected, and that nothing unexpected or untoward happens if API requests are formed incorrectly. For this, I’m going to use Cypress.

Cypress is a frontend testing tool built on JavaScript that executes tests via the browser. It’s extremely easy to set up, powerful, and user-friendly.

In the root directory of my projects, I’ll install Cypress using Node Package Manager by running:

$ npm install cypress --save-dev

…and run it with…

$ npx cypress open

Selecting ‘E2E testing’ and the Chrome browser opens a new instance of Chrome specifically for Cypress. Here, you can see a representation of the e2e folder created during installation which contains your test specs:

Heading across to VS Code, I’ll create a new .js file in this folder to start creating my tests in.

As I say, if you’re familiar with basic JavaScript, writing tests in Cypress is super intuitive. In the example below, I’ve described in line 1 what the overall suite of tests is intended to do (test my resume page, in this instance.) The first test then begins by declaring what the test does (in this case loads the page and looks for a phrase which I expect to be there.) It does this by calling cy.visit() to load the page, and then cy.contains(), to check the on-page content:

Saving the spec file automatically runs the test in Cypress, and the output shows as follows:

Great!

Cypress uses the DOM to find on certain page elements and report on whether they’re in the state you expect them to be. In the below example, the test uses cy.get() to find the element (a div in this case) with the ID ‘col1’. It then looks at that element’s CSS background-color property and asserts that it should match the expected hex code:

If it does, the test passes, if not it will fail. Here’s what it looks like when it fails:

What’s great about Cypress is that when there’s an issue, Cypress tells you very specifically what and where the issue is. Here, line 15 of my spec file expected that the background colour of that div should be #536978 (bluey-grey) but it’s actually #536979 (a very slightly different bluey-grey), and we can’t be having that! Also, that screenshot is killing my OCD so here’s it having passed after this grievous error was corrected:

Phew!

It’s possible to pass certain options to Cypress, both at the macro level and in individual tests. Here, I’m checking that the visitor counter loads its text, and that it updates the ‘count’ div with an integer. One thing I found was that this test would sometimes fail due to Cloud Run starting up from cold, and taking longer than the 400ms default timeout for tests. I solved this by adding the {defaultCommandTimeout: 10000} option (whilst making a mental note to investigate ways to minimise Cloud Run cold starts!)

One thing that’s not initially very intuitive with Cypress is how it handles variable assignments. I built a separate suite of tests for my API specifically and I wanted one of the tests to check that the visits count was incrementing as expected. I planned to call the API once and assign the returned visitor count to one variable, then call it again, assign the result to a second variable, then assert that var2 > var1. The issue was that (as this fantastic post explains) you in fact cannot assign the return value of a Cypress command to a variable, as each command in Cypress in fact runs asynchronously. The ‘Cypress way’ to work with variables is to ‘wrap’ them , and then ‘get’ them when you need to work on them later:

I added a bunch more tests on both the front and back ends of my project. Having built all this out, it’s time to look at describing my GCP resources with Infrastructure As Code…