Mastering PHPUnit: Writing Your First Test with PHPUnit
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.