This section provides an overview of what phpunit is, and why a developer might want to use it.
It should also mention any large subjects within phpunit, and link out to the related topics. Since the Documentation for phpunit is new, you may need to create initial versions of those related topics.
Version | Support ends | Supported in those PHP versions | Release date |
---|---|---|---|
5.4 | 2016-08-05 | PHP 5.6, PHP 7. | 2016-06-03 |
4.8 | 2017-02-03 | PHP 5.3, PHP 5.4, PHP 5.5, and PHP 5.6. | 2015-08-07 |
Imagine that we have a class Math.php
with logic of calculating of fiobanacci and factorial numbers. Something like this:
<?php
class Math {
public function fibonacci($n) {
if (is_int($n) && $n > 0) {
$elements = array();
$elements[1] = 1;
$elements[2] = 1;
for ($i = 3; $i <= $n; $i++) {
$elements[$i] = bcadd($elements[$i-1], $elements[$i-2]);
}
return $elements[$n];
} else {
throw new
InvalidArgumentException('You should pass integer greater than 0');
}
}
public function factorial($n) {
if (is_int($n) && $n >= 0) {
$factorial = 1;
for ($i = 2; $i <= $n; $i++) {
$factorial *= $i;
}
return $factorial;
} else {
throw new
InvalidArgumentException('You should pass non-negative integer');
}
}
}
We want to test logic of methods fibonacci
and factorial
. Let's create file MathTest.php
into the same directory with Math.php
. In our code we can use different assertions. The simplest code will be something like this (we use only assertEquals
and assertTrue
):
<?php
require 'Math.php';
use PHPUNIT_Framework_TestCase as TestCase;
// sometimes it can be
// use PHPUnit\Framework\TestCase as TestCase;
class MathTest extends TestCase{
public function testFibonacci() {
$math = new Math();
$this->assertEquals(34, $math->fibonacci(9));
}
public function testFactorial() {
$math = new Math();
$this->assertEquals(120, $math->factorial(5));
}
public function testFactorialGreaterThanFibonacci() {
$math = new Math();
$this->assertTrue($math->factorial(6) > $math->fibonacci(6));
}
}
We can run this test from console with command phpunit MathTest
and output will be:
PHPUnit 5.3.2 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 88 ms, Memory: 10.50Mb
OK (3 tests, 3 assertions)
A test method can accept arbitrary arguments. These arguments are to be provided by a data provider method. The data provider method to be used is specified using the @dataProvider
annotation.
:
<?php
require 'Math.php';
use PHPUNIT_Framework_TestCase as TestCase;
// sometimes it can be
// use PHPUnit\Framework\TestCase as TestCase;
class MathTest extends TestCase {
/**
* test with data from dataProvider
* @dataProvider providerFibonacci
*/
public function testFibonacciWithDataProvider($n, $result) {
$math = new Math();
$this->assertEquals($result, $math->fibonacci($n));
}
public function providerFibonacci() {
return array(
array(1, 1),
array(2, 1),
array(3, 2),
array(4, 3),
array(5, 5),
array(6, 8),
);
}
}
We can run this test from console with command phpunit MathTest
and output will be:
PHPUnit 5.3.2 by Sebastian Bergmann and contributors.
...... 6 / 6 (100%)
Time: 97 ms, Memory: 10.50Mb
OK (6 tests, 6 assertions)
<?php
require 'Math.php';
use PHPUNIT_Framework_TestCase as TestCase;
// sometimes it can be
// use PHPUnit\Framework\TestCase as TestCase;
We can test whether an exception is thrown by the code under test using method expectException()
. Also in this example we add one failed test to show console output for failed tests.
<?php
require 'Math.php';
use PHPUNIT_Framework_TestCase as TestCase;
// sometimes it can be
// use PHPUnit\Framework\TestCase as TestCase;
class MathTest extends TestCase {
public function testExceptionsForNegativeNumbers() {
$this->expectException(InvalidArgumentException::class);
$math = new Math();
$math->fibonacci(-1);
}
public function testFailedForZero() {
$this->expectException(InvalidArgumentException::class);
$math = new Math();
$math->factorial(0);
}
}
We can run this test from console with command phpunit MathTest
and output will be:
PHPUnit 5.3.2 by Sebastian Bergmann and contributors.
.F 2 / 2 (100%)
Time: 114 ms, Memory: 10.50Mb
There was 1 failure:
1) MathTest::testFailedForZero
Failed asserting that exception of type "InvalidArgumentException" is thrown.
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
Also PHPUnit
supports sharing the setup code. Before a test method is run, a template method called setUp() is invoked. setUp()
is where you create the objects against which you will test. Once the test method has finished running, whether it succeeded or failed, another template method called tearDown()
is invoked. tearDown()
is where you clean up the objects against which you tested.
<?php
require 'Math.php';
use PHPUNIT_Framework_TestCase as TestCase;
// sometimes it can be
// use PHPUnit\Framework\TestCase as TestCase;
class MathTest extends TestCase {
public $fixtures;
protected function setUp() {
$this->fixtures = [];
}
protected function tearDown() {
$this->fixtures = NULL;
}
public function testEmpty() {
$this->assertTrue($this->fixtures == []);
}
}
There are much more great opportunities of PHPUnit
which you can use in your test. For more info see in official manual
Class for which you will create unit test case.
class Authorization {
/* Observer so that mock object can work. */
public function attach(Curl $observer)
{
$this->observers = $observer;
}
/* Method for which we will create test */
public function postAuthorization($url, $method) {
return $this->observers->callAPI($url, $method);
}
}
Now we did not want any external interaction of our test code thus we need to create a mock object for callAPI function as this function is actually calling external url via curl.
class AuthorizationTest extends PHPUnit_Framework_TestCase {
protected $Authorization;
/**
* This method call every time before any method call.
*/
protected function setUp() {
$this->Authorization = new Authorization();
}
/**
* Test Login with invalid user credential
*/
function testFailedLogin() {
/*creating mock object of Curl class which is having callAPI function*/
$observer = $this->getMockBuilder('Curl')
->setMethods(array('callAPI'))
->getMock();
/* setting the result to call API. Thus by default whenver call api is called via this function it will return invalid user message*/
$observer->method('callAPI')
->will($this->returnValue('"Invalid user credentials"'));
/* attach the observer/mock object so that our return value is used */
$this->Authorization->attach($observer);
/* finally making an assertion*/
$this->assertEquals('"Invalid user credentials"', $this->Authorization->postAuthorization('/authorizations', 'POST'));
}
}
Below is the code for curl class(just a sample)
class Curl{
function callAPI($url, $method){
//sending curl req
}
}
Given a simple PHP class:
class Car
{
private $speed = 0;
public getSpeed() {
return $this->speed;
}
public function accelerate($howMuch) {
$this->speed += $howMuch;
}
}
You can write a PHPUnit test which tests the behavior of the class under test by calling the public methods and check whether they function as expected:
class CarTest extends PHPUnit_Framework_TestCase
{
public function testThatInitalSpeedIsZero() {
$car = new Car();
$this->assertSame(0, $car->getSpeed());
}
public function testThatItAccelerates() {
$car = new Car();
$car->accelerate(20);
$this->assertSame(20, $car->getSpeed());
}
public function testThatSpeedSumsUp() {
$car = new Car();
$car->accelerate(30);
$car->accelerate(50);
$this->assertSame(80, $car->getSpeed());
}
}
Important parts:
$this->assert...
functions to check expected values.The practice of replacing an object with a test double that verifies expectations, for instance asserting that a method has been called, is referred to as mocking.
Lets assume we have SomeService to test.
class SomeService
{
private $repository;
public function __construct(Repository $repository)
{
$this->repository = $repository;
}
public function methodToTest()
{
$this->repository->save('somedata');
}
}
And we want to test if methodToTest
really calls save
method of repository. But we don't want to actually instantiate repository (or maybe Repository
is just an interface).
In this case we can mock Repository
.
use PHPUnit\Framework\TestCase as TestCase;
class SomeServiceTest extends TestCase
{
/**
* @test
*/
public function testItShouldCallRepositorySavemethod()
{
// create an actual mock
$repositoryMock = $this->createMock(Repository::class);
$repositoryMock->expects($this->once()) // test if method is called only once
->method('save') // and method name is 'save'
->with('somedata'); // and it is called with 'somedata' as a parameter
$service = new SomeService($repositoryMock);
$service->someMethod();
}
}