Daniel EnmanEngineering @ Manifold

How to write tests and maintain your sanity

How to write tests and maintain your sanity

Testing is an important part of any project — be it manual or automated tests. Testing is how you tell if the code you wrote does what you expect. If you write code and never test it, you only know your code runs (if it compiles properly), but not that it works. Without testing, why even write code? Your users probably won’t be happy, if they can even use your product.

In some cases, manual testing is sufficient. You probably aren’t going to run your full test suite after you make every change (but you might run the one off manual test!), but you are most likely going to try it out for yourself, and check your application manually. This can work well with small changes, or on a small project, but as the project quickly builds, you quickly outgrow manual testing, and look at ways to automate some testing of it.

Types of Tests

Before you get to the stage where you can start to run tests, you need to write them. Testing patterns span from unit tests, to integration testing of units, to full end-to-end testing. It is certainly a delicate balance to have the right amount of each kind of test in the product.

From Google’s testing blog: [https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html)From Google’s testing blog: https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html

Unit Tests

Unit tests are small, run quickly and can tell if a single unit of code (a component, a class, a function, etc) behaves properly given some inputs.

Unit tests are an extremely valuable tests for testing your code. Before looking into integration testing or end-to-end testing, unit tests should be first.

Integration Tests

Integration tests “level up” the unit tests, by taking multiple “units” and checking how they interact together.

End-to-End Tests (E2E)

End-to-end tests are more comprehensive, but are also much heavier in terms of runtime and requirements. E2E tests tend to be more “flakey” because they rely on a simulated browser environment (that may or may not be like a real browser), and they are prone to breaking unnecessarily if the page structure changes, without updating the E2E test.

What are E2E tests good for?

After reading the above, you might say to yourself, E2E tests sound good in theory, but they don’t sound like the best go-to for testing. Hey, you’re a smart person. E2E tests are good, but there is an important balance to find with them. If you checked out the Google Testing blog for the image linked above, you might be cautiously optimistic about E2E testing, and for good reason; E2E tests should not be the largest part of your testing suite, but they do play an important role in testing.

When doing a software release, there is often a QA checklist that must be run through before the release is deployed. This could include a manual check on registration, performing certain user and product actions, and/or ensuring the system works from the perspective of a user. These checklists can take time to work though, depending on the changes made for the release and the size or scope of the product. E2E testing can help automate the QA checklist, to ensure the user’s experience of the product is a good one.

Writing and Running E2E tests

Now that we know a little bit more about testing, and when to use what type of test, let’s write some simple E2E tests for the Manifold Laravel Demo.

Getting Started

Follow the steps in the repository README or the introductory blog post. Once the demo app is running, let’s install Cypress.io run our E2E tests. This Cypress.io is a JavaScript end-to-end testing framework, that runs end-to-end tests on web-based software. Cypress has a simplistic API, does automatic waiting, allows network stubbing, does screenshot and video recording, and more. Install cypress:

npm install cypress

Once installed you can use npx cypress to run Cypress in your node_modules/ directory. Running npx cypress open will open Cypress’ optional GUI component, and get a directory structure started for you, by creating a cypress/ directory, and adding some basic tests and helpers.

Our first test

Let’s get started writing our first test. You can remove the file cypress/integration/example_spec.js, however it does have some great examples on how to use Cypress.

Create a new test file, that we can use to build our E2E tests for the Laravel demo. Open in your favorite editor the file cypress/integration/demo.js. Cypress will manage the global variables for you, and the test pattern is borrowed from Mocha, so tests should be familiar. Each test suite should have a description, and a series of test actions. The functions to handle this flow are called describe and it.

Let’s write a test to make sure the of our page is correct. Maybe this is a good sanity check to ensure that the application loaded properly, and did not error during startup.

describe('sanity check', () => {
  it('the title should match', () => {
    cy.visit('http://localhost:8000');

    cy.title().should('include', 'Laravel');
  });
});

If you run this using npx cypress open, you’ll see something like this:

Pretty cool, right?

Let’s write a test to register and check the login.

const uniqueEmailTag = () => Math.floor(Math.random() * 1000000);

describe('registration and login', () => {
  let email;

  before(() => {
    // generate a unique email -- you could use something like GuerrillaEmail to gerate
    // a disposable address.
    email = `demo+${uniqueEmailTag()}@example.com`
  });


  beforeEach(() => {
    Cypress.Cookies.preserveOnce('laravel_session');
  });

  it('you can reach the registration and login pages from the homepage', () => {
    cy.visit('http://localhost:8000');

    cy.get('a[href*=login]').click();
    cy.location('pathname').should('include', 'login')
      .then(() => {
        cy.go('back');
      });

    cy.get('a[href*=register]').click();
    cy.location('pathname').should('include', 'register');
  });

  it('entering valid registration information should register me', () => {
    cy.visit('http://localhost:8000/register');

    cy.get('#name').type('Demo User');
    cy.get('#email').type(email);
    cy.get('#password').type('password');
    cy.get('#password-confirm').type('password');
    cy.get('button[type="submit"]').click().then(() => {
      cy.location('pathname').should('include', 'home');
    })
  });

  it('logging out and logging back in works', () => {
    cy.visit('http://localhost:8000/home');

    cy.get('a[href="#"].dropdown-toggle')
      .click()
      .then(() => {
        cy.get('a[href*="logout"').click()
          .then(() => {
            cy.location('pathname').should('eq', '/');
          })
      });

    cy.get('a[href*=login]').click()
      .then(() => {
        cy.get('#email').type(email);
        cy.get('#password').type('password');

        cy.get('button[type="submit"]').click()
          .then(() => {
            cy.location('pathname', 'home');
          })
    });
  });
});

Recording Runs

Cypress offers a Dashboard service, which allows you to record test runs in a central spot, and to review any screenshots or a video recording of the test. The test requires an API key to record the run, so let’s use the Manifold to store this configuration, so we can easily record runs by developers and your CI system. Check out how to get started with our CLI here.

Let’s create a custom resource to store the configuration:

manifold projects create cypress-io — title cypress-io

manifold create — project cypress-io -c cypress

manifold config set — resource cypress CYPRESS_PROJECT_ID=YOUR-ID-HERE

manifold config set — resource cypress CYPRESS_RECORD_KEY=YOUR-KEY-HERE

Once the configuration is set, you can run the tests with manifold run --project cypress-io npx cypress and the configuration for Project ID and Record Key will be injected.

Write those tests!

Now that you can write E2E tests, go write some! Remember there is a balance in testing, especially with tests that can be fragile or take a long time to finish. Prefer unit tests, but make sure you test your system as a whole too. End to end tests can give you the confidence your team needs when shipping, that the system works together properly.

StratusUpdate

Sign up for the Stratus Update newsletter

With our monthly newsletter, we’ll keep you up to date with a curated selection of the latest cloud services, projects and best practices.
Click here to read the latest issue.