Mastering PHPUnit: Writing Your First Test with PHPUnit

May 17, 2024·
Gert de Pagter
Gert de Pagter
· 4 min read

This is part 2 of a series on PHPUnit testing, you can find part 1 here. This post will have us writing our first unit test, and explain some of the basics in doing so.

TestCase class

Every test we write has to extend the PHPUnit\Framework\TestCase class. PHPUnit will look for classes in its configured folders that extend from this class, to execute tests. Within your class it will look for all methods that have the PHPUnit\Framework\Attributes\Test attribute (#[Test]), or where the name of the method starts with test. If either of those is true, PHPUnit will consider it a test, and run it as such.

PHPUnit assertions

As mentioned in the first post of this series, we want to split up our code in three parts, arrange, act and assert. Especially for this third step, the assert, there are a lot of options built right into PHPUnit. We have a lot of assertions out of the box. To name some:

  • assertSame
  • assertNotSame
  • assertEquals
  • assertTrue
  • assertFalse
  • assertNull

And many more.

Generally you want to use the most specific assertions. So instead of doing self::assertSame(true, $myValue), you would want to use self::assertTrue($myValue). If you are using a tool like PHPStan, you can use the phpunit extension to automatically detect when you can use these.

An assert call does what you expect it to, it asserts that something is what you want it to be. With assertTrue you assert your value is true. With assertSame you assert 2 variables are the same, with assertNotSame you assert they are not the same.

What is important to know about these assertions is that they throw a specific PHPUnit exception if they fail, so test execution stops at that point. So if you take the following code example, the self::assertSame(3,4); will not be executed.

self::assertTrue(false);
self::assertSame(3,4);

For a full list of all the possible assertions, you can look at the official documentation

Avoiding code duplication it tests

A lot of the time, your tests will share most of their setup. In case of more legacy projects that need a lot of setup to run, you might want to extract that. To facilitate that, you can override the following methods in your test class:

  • setUp
  • tearDown
  • setUpBeforeClass (static)
  • tearDownAfterClass (static)

The first method to be called is setUpBeforeClass this is a static method that is called before any test are ran. Next the setUp is called, which is called before every test itself, so if you have 4 tests in your class, setUp will be called 4 times, just before each test is started.

After the test is finished tearDown is called, in the same matter as setUp, it’s called every time a test is finished. And lastly, once all tests fo the class are finished, tearDownAfterClass is called.

If you are familiar with a javascript framework like Jest, the setUp and tearDown are the PHPUnit equivalent of beforeEach and afterEach.

Writing a test with setUp and tearDown

Let’s say we have some code that looks like this. I know it doesn’t look good, but it is common in older applications to have global variables that are manipulated.

$globalVariable = 0;
function globalManipulation (): int {
    global $globalVariable;

    $globalVariable++;

    return $globalVariable;
}

Let’s write some tests for this code. We’ll have one method where we call it once, (and then it should be 1). And then another method where we call it twice,and it should be two.

class TestGlobalFunction extends TestCase
{
    public function testItIsOne()
    {
        $value = globalManipulation();

        $this->assertSame(1, $value);
    }

    public function testItIsTwo()
    {
        globalManipulation();
        $value = globalManipulation();

        $this->assertSame(2, $value);
    }
}

Now if we run the test we get an output which tells us we have a failure.

There was 1 failure:

1) tests\TestGlobalFunction::testItIsTwo
Failed asserting that 3 is identical to 2.

The reason that the value is 3, and not 2, is because we haven’t reset the $globalVariable, so let’s implement setUp and tearDown to reset the variable each time.

class TestGlobalFunction extends TestCase
{
    protected function setUp(): void
    {
        global $globalVariable;
        $globalVariable = 0;
    }

    protected function tearDown(): void
    {
        global $globalVariable;
        $globalVariable = 0;
    }

    public function testItIsOne()
    {
        $value = globalManipulation();

        $this->assertSame(1, $value);
    }

    public function testItIsTwo()
    {
        globalManipulation();
        $value = globalManipulation();

        $this->assertSame(2, $value);
    }
}

Now as you can see we added the logic to both setUp and tearDown. In the setUp we change it so we know our tests start with a predictable state. In the tearDown we set it, so we know our test don’t influence other tests.

In next weeks post we’ll discuss Data Providers in PHPUnit, which will help us write a lot of test cases in a short time, and reduce code duplication. If you want to get notified of the next blog post, join the newsletter.