Nona Blog

An introduction to assert with PHPUnit

This article serves as the first of a series on unit testing in PHPUnit aimed at providing a fundamental understanding of unit tests at a basic level. To do this I’ll be going through an example with PHPUnit. First I’ll be showing you how to setup PHPUnit and introducing assertions (a core part of tests). Then I’ll dive in to how and why we write unit tests.

A brief setup of PHPUnit

  1. First install PHPUnit (I will assume you have php ~7 installed on your system)

    https://phpunit.de/getting-started/phpunit-7.html

  2. Next, verify that it has been installed: run the following in your terminal.
phpunit --v

3. Create a new file called HelloWorld.test.php in a folder on your machine and populate it with the following:

<?php

  use PHPUnit\Framework\TestCase;

  class HelloWorldTest extends TestCase {}

Here we are creating a testable class that extends the PHPUnit base TestCase class.

4. Now, let’s create some configuration in the form of a phpunit.xml file with the following contents:

<?xml version="1.0" encoding="utf-8" ?>
<phpunit>
    <testsuite name='Hello World Test Suite'>
        <directory suffix='.test.php'>./</directory>
    </testsuite>
</phpunit>

This will allow us to define which files will be tested when we run phpunit and add a brief description as to what can be found in this particular test suite.

5. Next, run phpunit in your terminal

You should see that the tests ran, but with a warning (as we haven’t written any tests yet). At this point we can start writing our tests.

WARNINGS!
Tests: 1, Assertions: 0, Warnings: 1.


Before we start looking at assertions let’s take a look at why and how we write unit tests.

Why do we write unit tests?

We write unit tests to make sure that isolated pieces of functionality (functions) work as we expect outside of the context of the rest of the application. The way in which we write our tests should also affect the way we write our code. What you should start to notice over time is that you start writing functions differently and in a way that can be better tested. This usually amounts to purer functions (with little to no side-effects) being written. If your focus is already on writing pure functions then you have already won half the battle!

If our tests pass, and assuming we have written them correctly, we can be confident that our code will work as expected. This also means that we need to write sufficient tests to cover all possible scenarios, including some which might be out of the ordinary (fringe cases). We ideally also want to account for failures and expectant error handling , I will be touching on both of these later on in the series.

How do we go about doing this?

Traditionally the how in unit tests relates to “Test Driven Development” (TDD), to write tests that drive the rest of the code. This means a few things for us:

  1. We usually write our tests before we write the code that will eventually pass the test written for it
  2. We never write more code (or tests) than needed
  3. Our tests need to be specific and inclusive enough to cover the requirements of our app, which includes all realistic good and bad scenarios

Let’s write a basic test to make sure that our app does what we expect it to do.

We will start by writing a test that is going to fail, because it does not do what we expect it to do, we will then write just enough code to make that test pass.

Our first assertion

We can assert (confirm) that something is as expected, by writing a test assertion.

Lets replace the current contents of our HelloWorld.test.php file with the following:

<?php

  use PHPUnit\Framework\TestCase;

  class HelloWorldTest extends TestCase {
    public function test_helloWorld_returns_value_as_expected() {
      $this->assertEquals('hello', helloWorld());
    }
  }

In line 6 we are creating a function with a name that describes what that function is testing, which in this case when translated into english equates to ‘test helloWorld returns value as expected’. Something important to note is that in PHPUnit the test will not run out-of-the-box unless the function name is prefixed with the word ‘test’. Some people prefer to use comments to describe what a test is doing, however, I am not going to get into the semantics of writing descriptive testing patterns in this article.

In line 7 we are asserting that whatever is returned from a function named HelloWorld equates to ‘hello’. This is our test assertion.

Lets run our test to see if it passed by once again running: phpunit

Oops … it looks like it failed.

There was 1 error:
1) HelloWorldTest::test_helloWorld_returns_value_as_expected
Error: Call to undefined function helloWorld()
/phpunit/HelloWorld.test.php:7
ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

Well of course it failed,

  1. No function with the name helloWorld exists
  2. The return value of this non existent function is also not equal to ‘hello’

Thankfully, we expected our assertion (‘assertEquals`) should fail. If our test passed at this point, then we should suspect that something went wrong.

Let’s define a function called helloWorld at the top of the same file which returns the value ‘hello’ and then run the test again ( phpunit in your terminal).

<?php
  function helloWorld() {
    return 'hello';
  }

  use PHPUnit\Framework\TestCase;

  class HelloWorldTest extends TestCase {
    public function test_helloWorld_returns_value_as_expected() {
      $this->assertEquals('hello', helloWorld());
    }
  }
.                                                                   
1 / 1 (100%)
Time: 79 ms, Memory: 8.00MB
OK (1 test, 1 assertion)


Eureka, our test passed!

Great news, we have covered basic assertions and written our first passing test in PHPUnit!

Richard

Richard

Add comment