Brandbrand
Book a call
Glasses in front of a screen full of code, with the code sharp through the lenses

What unit tests are, and how we test before delivery

A unit test checks one small piece of code, separate from the rest. What that gives you, where it stops, and how we use Pest and CI before anything goes live.

One piece of code, one expectation

A unit test takes the smallest piece of code that does something meaningful, usually one function or class, and checks it with a fixed input and an expected outcome. No database, no network, no other systems. So a test runs in milliseconds, and the whole set runs in seconds.

Take the check that a webhook really comes from Shopify. Shopify adds a signature to every message. If it does not match, the app has to reject the message. That is exactly the kind of rule you want to get right once and never break by accident again.

// Two expectations: a real signature matches, a changed message does not.
it('accepts a webhook signed by Shopify', function () {
    $body = '{"id":1001}';
    $hmac = base64_encode(hash_hmac('sha256', $body, 'secret', true));

    expect(ShopifyHmac::valid($body, $hmac, 'secret'))->toBeTrue();
});

it('rejects a webhook whose message was changed', function () {
    $hmac = base64_encode(hash_hmac('sha256', '{"id":1001}', 'secret', true));

    expect(ShopifyHmac::valid('{"id":1002}', $hmac, 'secret'))->toBeFalse();
});
tests/Unit/ShopifyHmacTest.php

Unit, feature and end-to-end

Unit tests are the foundation, but not the whole story. You test at three levels:

  • Unit tests check one piece of code on its own. You have many of them, and they are fast.
  • Feature tests send a request through the app, such as an incoming webhook, and check what happens in the database and on the queue. In Laravel you replace Shopify and other systems with fakes, so a test never touches a real store.
  • End-to-end tests go through the app the way a user does, in a real browser. They are slow and brittle, so you have few of them, for the routes that really must not fail.

The test first

Where we can, we write the test before the code exists. The test fails first, because the function is not there yet. Then we write just enough code to make it pass. That way you know the test checks something, and that the code does what was asked, no more.

A bug works the same way. First a test that shows the error, then the fix. The test then stays in the set, so the same error does not come back.

How we test before delivery

We test our Shopify apps with Pest, a testing framework for PHP that is built on PHPUnit and fits well with Laravel. The way we work:

  • Every change goes through a pull request, so a colleague can read it before it joins the rest.
  • CI runs the tests on every pull request. A server fetches the code and runs the full set, apart from anyone's own laptop.
  • Red means stop. A pull request is only merged once all tests pass.

This site works the same way. Every pull request runs more than 80 unit tests, along with checks on code style and types. If one fails, no new version is built and nothing goes live.

What tests do not do

Tests check what you wrote down. Whether that is also what the store needs, they do not say. That is why delivery remains a moment when the merchant uses the app themselves, with real examples from their own business. Tests make sure that conversation is about the right things, and not about errors we should have found ourselves.

Our stance

A bug we find gets a test first. Only then a fix.
Brandbrand

Questions about this

What is a unit test?

A small, automatic check of one piece of code, such as a function, with a fixed input and an expected outcome. No database or network, so it finishes in milliseconds.

What is the difference from a feature test?

A feature test sends a whole request through the app and looks at the result, such as what ends up in the database. A unit test looks at one piece of code on its own.

What is Pest?

A testing framework for PHP, built on PHPUnit, with a short syntax that reads well. Laravel supports it out of the box.

Do tests make an app more expensive?

They take time during the build. They earn it back with every change after that, because you do not have to check everything by hand again.