A Beginner’s Guide to PHPUnit: Writing and Running Unit Tests in PHP
Learn the Basics of the PHPUnit Testing Framework with Simple Examples and Best Practices
Introduction
PHPUnit is a popular testing framework for PHP. It is used by developers worldwide to write and run unit tests for their PHP code. Unit testing is a crucial part of software development that helps to ensure the quality and correctness of your code. With PHPUnit, you can write automated tests that check your code’s behavior, detect errors and regressions, and ensure that changes to your code do not break existing functionality.
PHPUnit is designed to be easy to use, even for developers who are new to testing. It provides a simple and intuitive syntax for defining test cases, writing test methods, and making assertions about your code’s output. Additionally, it includes advanced features like mocking and stubbing that allow you to simulate the behavior of external dependencies and test complex interactions between different parts of your code.
Using PHPUnit, you can save time and reduce the risk of introducing bugs into your code. By catching errors early in the development process, you can avoid costly bugs that might be harder to fix later on. Furthermore, writing tests can help you understand your code better and make it more maintainable over time.
In this article, we’ll walk you through the basics of using PHPUnit to write and run unit tests in PHP. We’ll cover everything you need to know to get started, from setting up PHPUnit to writing tests and using advanced features like mocking and stubbing. By the end of this article, you’ll have a solid foundation in PHPUnit and unit testing in general, and you’ll be able to confidently start testing your own PHP code.
Who This Article is For and What You Will Learn:
This article is designed for PHP developers new to unit testing and who want to learn how to use PHPUnit to test their code. If you’re already familiar with testing and PHPUnit, this article may still be a helpful refresher or introduction to best practices.
This article will teach you how to install and configure PHPUnit, write and run basic tests using PHPUnit, and use advanced features like mocking and stubbing. We’ll cover topics like test fixtures, data providers, and best practices for writing practice tests.
Whether you’re working on a small or large PHP project, testing your code with PHPUnit is essential for improving its quality and ensuring its correctness. This article will provide you with the knowledge and tools you need to start writing practical unit tests and improve the overall quality of your PHP code.
Setting up PHPUnit
Setting Up Your PHP Project and Installing PHPUnit with Composer:
Before you can start writing tests with PHPUnit, you’ll need to set up your PHP project and install PHPUnit using Composer. Here’s a step-by-step guide to getting started:
- Create a new PHP project: First, create a new directory for your PHP project. For example, you could create a directory called “phpunit-intro”.
- Install Composer: Next, you’ll need to install Composer, a popular package manager for PHP. You can download Composer from the official website (https://getcomposer.org/download/), or you can install it using your operating system’s package manager for example brew on macOS
Use the composer config generator to generate the composer.json file
composer init
Use the defaults for all questions besides (Would you like to define your dependencies (require) interactively = no) for the last question use the following answer Search for a package: phpunit/phpunit and install the latest version.
Your final file should look similar to the following, please adjust the autoload part:
{
"name": "patricgutersohn/phpunit-intro",
"require": {
"phpunit/phpunit": "^10.0"
},
"autoload": {
"classmap": [
"src"
]
},
"authors": [
{
"name": "patricgutersohn"
}
]
}
Run the following command:
composer dump-autoload
Finally, create a new file in your project directory called “phpunit.xml”. This file will contain the configuration settings for PHPUnit, such as the location of your test files and the output format for test results.
Here’s an example configuration file:
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="My Project Tests">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
In this example, we’ve specified that our tests are located in the “tests” directory, and we’ve set the bootstrap file to “vendor/autoload.php”, which is generated by Composer and contains the autoloader for our project’s dependencies.
Create a bootstrap.php file in the src folder with the following content:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
With these steps, you should now have a PHP project set up with PHPUnit installed and configured. In the next section, we’ll cover how to write and run basic tests using PHPUnit.
Writing and Running Tests
Writing a Test Case in PHPUnit:
A test case in PHPUnit is a class that contains one or more test methods. Each test method is responsible for testing a specific aspect of your code and uses assertions to verify that the expected behavior is met.
To create a test case in PHPUnit, follow these steps:
- Create a new file for your test case: In your project directory, create a new directory called “tests”. Inside this directory, create a new PHP file with a name that reflects the class you want to test. For example, if you want to test a class called “MyClass”, you could create a file called “MyClassTest.php”.
- Define your test case class: In your new file, create a new class that extends the PHPUnit\Framework\TestCase class. This will give your test case access to all the functionality provided by PHPUnit.
<?php
use PHPUnit\Framework\TestCase;
require_once 'src/bootstrap.php';
class MyClassTest extends TestCase
{
}
3. Define your test methods: Inside your test case class, define one or more test methods. Each test method should start with the word “test” and should contain the code that tests a specific aspect of your code.
<?php
use PHPUnit\Framework\TestCase;
require_once 'src/bootstrap.php';
class MyClassTest extends TestCase
{
public function testConcatenateStrings()
{
$myClass = new MyClass();
$str1 = 'hello';
$str2 = 'world';
$expectedResult = 'helloworld';
$result = $myClass->concatenateStrings($str1, $str2);
$this->assertEquals($expectedResult, $result);
}
}
In this example, we’ve defined a test method called “testStringComparison”. Inside the test method, we’ve defined two strings and used two assertions to test that the first string is equal to “hello” and the second string is not equal to “hello”.
Here is the MyClass code that we are testing:
// location: src/MyClass.php
<?php
class MyClass
{
public function concatenateStrings($str1, $str2)
{
return $str1 . $str2;
}
}
4. Run your test case: To run your test case, use the following command in your terminal:
./vendor/bin/phpunit
This will run all the test cases in your project. If everything is working correctly, you should see a summary of the test results, including the number of tests that passed and failed. The output will look similar to the following:
With these steps, you should now be able to write and run basic test cases using PHPUnit.
To see a failing test you could adjust the MyClass to following
<?php
class MyClass
{
public function concatenateStrings($str1, $str2)
{
return $str1;
}
}
Run your test cases again with
./vendor/bin/phpunit
That will provide you with a very detailed and helpful output of why your test is failing in the Expected / Actual comparison like below:
Test Fixtures and Data Providers
In PHPUnit, a “test fixture” is a set of data that is used by one or more test methods to ensure consistent and repeatable testing. A test fixture can include things like objects, variables, or database records.
To define a test fixture in PHPUnit, you can use the setUp
and tearDown
methods, which are executed before and after each test method, respectively. The setUp
method is used to initialize the test fixture, while the tearDown
method is used to clean up any resources that were used during testing.
Here’s an example of a test fixture for the MyClass
class:
<?php
use PHPUnit\Framework\TestCase;
require_once 'src/bootstrap.php';
class MyClassTest extends TestCase
{
private $myClass;
public function setUp(): void
{
$this->myClass = new MyClass();
}
public function tearDown(): void
{
// Clean up any resources used during testing
}
public function testConcatenateStrings()
{
$result = $this->myClass->concatenateStrings('hello', 'world');
$this->assertEquals('helloworld', $result);
}
}
In this example, we’ve defined a private property called $myClass
, which is an instance of the MyClass
class. In the setUp
method, we've initialized this property by creating a new instance of the MyClass
class. In the tearDown
method, we could clean up any resources used during testing (although there aren't any in this example).
We’ve also defined a test method called testConcatenateStrings
, which uses the $myClass
property to test the concatenateStrings
method, just like in the previous example.
In addition to test fixtures, PHPUnit also supports “data providers”, which allow you to run the same test method with multiple sets of input data. To define a data provider in PHPUnit, you can use the @dataProvider
annotation to specify a method that returns an array of data.
Here’s an example of a data provider for the MyClass
class:
<?php
use PHPUnit\Framework\TestCase;
require_once 'src/bootstrap.php';
class MyClassTest extends TestCase
{
private $myClass;
public function setUp(): void
{
$this->myClass = new MyClass();
}
public function tearDown(): void
{
// Clean up any resources used during testing
}
/**
* @dataProvider concatenationDataProvider
*/
public function testConcatenateStrings($str1, $str2, $expected)
{
$result = $this->myClass->concatenateStrings($str1, $str2);
$this->assertEquals($expected, $result);
}
public static function concatenationDataProvider()
{
return [
['hello', 'world', 'helloworld'],
['foo', 'bar', 'foobar'],
['', '', ''],
];
}
}
In this example, we’ve defined a new method called concatenationDataProvider
, which returns an array of arrays. Each sub-array contains three elements: the first string to be concatenated, the second string to be concatenated, and the expected result. We've then added the @dataProvider
annotation to the testConcatenateStrings
method to specify that it should use this data provider.
When the testConcatenateStrings
the method is executed, PHPUnit will run it once for each set of data returned by the concatenationDataProvider
method, passing in the three array elements as parameters. This allows us to test the concatenateStrings
method with multiple sets of input data, without having to write multiple test methods.
Mocking and Stubbing
In PHPUnit, “mocking” and “stubbing” are techniques for creating fake objects that simulate the behavior of real objects. These techniques are useful for testing code that depends on external services, such as databases or web services, or for testing complex interactions between objects.
Mocking involves creating a fake object that can be used in place of a real object during testing. This fake object will behave in a predictable way, allowing you to test your code without depending on the behavior of the real object.
In PHPUnit, you can use the getMockBuilder
method to create a mock object. Here's an example:
<?php
use PHPUnit\Framework\TestCase;
require_once 'src/bootstrap.php';
class MyClassTest extends TestCase
{
// ...
public function testCallsApiMethod()
{
$myClassMock = $this->getMockBuilder(MyClass::class)
->onlyMethods(['apiCallMethod'])
->getMock();
$myClassMock
->expects($this->once())
->method('apiCallMethod')
->willReturn(true);
$result = $myClassMock->someMethod();
$this->assertTrue($result);
}
}
In this example, we’ve used the getMockBuilder
method to create a mock object of the MyClass
class. We've specified that we only want to mock the apiCallMethod
method, and we've then used the expects
method to configure the behavior of the mock object. We've specified that we expect the apiCallMethod
method to be called once and that it should return true
when it is called. This method could call an external API, we wanna create a mock so our unit test doesn't depend on a not predictable response.
We’ve then called the someMethod
method of the mock object and stored the result in the $result
variable. Finally, we've used the assertTrue
assertion to verify that the result is true
.
Here’s an example of using a stub object to test the MyClass
class:
<?php
use PHPUnit\Framework\TestCase;
require_once 'src/bootstrap.php';
class MyClassTest extends TestCase
{
// ...
public function testUsesAStud()
{
$myClassStub = $this->getMockBuilder(MyClass::class)
->onlyMethods(['getSomeValue'])
->getMock();
$myClassStub->method('getSomeValue')->willReturn(42);
$result = $myClassStub->someMethodThatUsesSomeValue();
$this->assertEquals(42, $result);
}
}
In this example, we’ve used the getMockBuilder
method to create a stub object of the MyClass
class. We've specified that we only want to stub the getSomeValue
method, and we've then used the willReturn
method to configure the behavior of the stub object. We've specified that we want the getSomeValue
method to always return the value 42
when it is called.
We’ve then called the someMethodThatUsesSomeValue
method of the stub object and stored the result in the $result
variable. Finally, we've used the assertEquals
assertion to verify that the result is 42
.
Here is an example of how the methods could be implemented:
<?php
class MyClass
{
public function concatenateStrings($str1, $str2)
{
return $str1 . $str2;
}
public function someMethod()
{
$result = $this->apiCallMethod();
if ($result === true) {
return true;
}
return false;
}
public function apiCallMethod()
{
// calls external API
}
public function getSomeValue()
{
// some implementation here
}
public function someMethodThatUsesSomeValue()
{
$value = $this->getSomeValue();
return $value;
}
}
Mocking and stubbing are powerful techniques for testing classes.
A more in-depth explanation of the difference between mocking and stubbing:
In the first example, we’re testing the someMethod
method of the MyClass
class. The someMethod
method calls the apiCallMethod
method of the same class. We want to test that the someMethod
method behaves correctly when the apiCallMethod
method returns true
.
To test this behavior, we use a mock object of the MyClass
class. We mock the apiCallMethod
method and configure the mock to return true
when it is called. We do this because we want to test that the someMethod
method correctly handles the case where apiCallMethod
returns true
. By mocking, we can isolate someMethod
and test it in isolation.
In the second example, we’re testing the someMethodThatUsesSomeValue
method of the MyClass
class. The someMethodThatUsesSomeValue
method calls the getSomeValue
method of the same class. We want to test that the someMethodThatUsesSomeValue
method correctly uses the value returned by getSomeValue
.
To test this behavior, we use a stub object of the MyClass
class. We stub the getSomeValue
method and configure the stub to return the value 42
when it is called. We do this because we want to replace the real getSomeValue
method with a simplified version that always returns 42
. By stubbing, we can simplify the test and focus on testing the behavior someMethodThatUsesSomeValue
in isolation.
In general, you would use a mock object when you want to verify that a method of an object is called with certain arguments and returns a certain value, and you would use a stub object when you want to replace a method of an object with a simplified version that always returns a certain value. However, the distinction between mocking and stubbing can be subtle, and the terms are often used interchangeably in practice.
Best Practices
- Write testable code: Write code that is designed to be tested, with clear inputs and outputs, and minimal dependencies. If your code is not testable, it can be difficult or impossible to write effective tests for it.
- Write small, focused tests: Write tests that focus on a specific piece of functionality, and test one thing at a time. This makes it easier to isolate and fix problems when they occur.
- Use descriptive test names: Use descriptive names for your test methods, to make it clear what they are testing. This makes it easier to understand what the tests are doing and what they are testing for.
- Use assertions effectively: Use assertions to check that the expected results of your tests are correct. Be sure to check for both positive and negative cases, and use the appropriate assertions for the data types you are working with.
- Test edge cases and error conditions: Test edge cases and error conditions to make sure your code handles them correctly. This can include testing for things like empty arrays, null values, and unexpected input.
- Use test fixtures: Use fixtures to set up test data and resources, to make it easier to write tests and to avoid duplication of test code.
- Use data providers: Use data providers to test your code with a variety of input values, to make sure it works correctly in different scenarios.
- Keep tests fast and independent: Write tests that run quickly and independently of each other. This makes it easier to run tests frequently and to isolate problems when they occur.
- Refactor code when necessary: Refactor your code when necessary to make it more testable. This may involve breaking down large methods into smaller ones, reducing dependencies, or using dependency injection to make code more modular.
By following these best practices, you can write effective tests that help you find and fix problems in your code, and make it easier to maintain and extend your code over time.
Conclusion
PHPUnit is a powerful testing framework for PHP that allows you to write unit tests for your code. With PHPUnit, you can test your code for correctness and ensure that it behaves as expected.
In this article, we’ve covered the basics of PHPUnit, including how to install it, set up a project for testing, and write test cases. We’ve also covered more advanced topics like fixtures, data providers, and mocking.
By using PHPUnit to test your code, you can catch bugs early in the development process, and ensure that your code is working as expected. This can save you time and effort in the long run, and help you create higher-quality code.
So if you’re a PHP developer, it’s definitely worth taking the time to learn how to use PHPUnit effectively. With its powerful features and extensive documentation, you can quickly become an expert in writing tests for your PHP code, and make your development process smoother and more efficient.