First, clone this repository:
$ git clone https://github.com/fpondepeyre/workshop-phpunit.git
Then, run:
$ docker-compose up
Enter in the "app" container:
$ docker-compose run app bash
$ composer install
Run tests:
$ ./bin/phpunit
If phpunit is not installed
composer req test
Setup phpunit.xml.dist file, by example:
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/6.5/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
>
<php>
<ini name="error_reporting" value="-1" />
<env name="KERNEL_CLASS" value="App\Kernel" />
<env name="APP_ENV" value="test" />
<env name="APP_DEBUG" value="1" />
<env name="APP_SECRET" value="s$cretf0rt3st" />
<env name="SHELL_VERBOSITY" value="-1" />
</php>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>src</directory>
</whitelist>
</filter>
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
</listeners>
</phpunit>
- Your tests should mirror your codebase directly but within its own folder, and test files must match the file they are testing, with Test appended. In our example, if we had the following code
- Classnames are exactly the same as filenames. Whatever you have named your file should be the name of your class - which should be true for your non-test code anyway!
- Your test method names should start with test, in lower case. Method names should be descriptive of what is being tested, and should include the name of the method being tested. This is not a place for short, abbreviated method names.
- PHPUnit is unable to run tests that are either protected or private - they must be public. Likewise, any methods you create as helpers must be public. We are not building a public API here, we just want to write tests so do not worry about visibility.
- Your classes must extend the TestCase class, or extend a class that does.
First stupid test:
<?php
namespace App\Tests;
use PHPUnit\Framework\TestCase;
/**
* Class StupidTest
*/
class StupidTest extends TestCase
{
public function testTrueIsTrue()
{
$foo = true;
$this->assertTrue($foo);
}
}
List of assertions: https://phpunit.readthedocs.io/en/7.4/assertions.html
First test.
class SlugTest extends TestCase
{
public function testSluggifyReturnsSluggifiedString()
{
$originalString = 'This string will be sluggified';
$expectedResult = 'this-string-will-be-sluggified';
$slug = new Slug();
$result = $slug->sluggify($originalString);
$this->assertSame($expectedResult, $result);
}
Avoid duplication code
<?php
// ...
/**
* @dataProvider providerTestFoo
*/
public function testFoo($variableOne, $variableTwo)
{
//
}
public function providerTestFoo()
{
return array(
array('test 1, variable one', 'test 1, variable two'),
array('test 2, variable one', 'test 2, variable two'),
array('test 3, variable one', 'test 3, variable two'),
array('test 4, variable one', 'test 4, variable two'),
array('test 5, variable one', 'test 5, variable two'),
);
}
public function invokeMethod(&$object, $methodName, array $parameters = array())
{
$reflection = new \ReflectionClass(get_class($object));
$method = $reflection->getMethod($methodName);
$method->setAccessible(true);
return $method->invokeArgs($object, $parameters);
}
Code coverage report
./vendor/bin/phpunit --coverage-html coverage
Pay attention to your CRAP
Read doc here https://phpunit.readthedocs.io/en/7.4/test-doubles.html
PHPUnit comes with a very powerful feature to help us handle outside dependencies. It basically involves replacing the actual object with a fake, or ‘mock’, object that we fully control, removing all dependencies on outside systems or code that we really have no need to test.
Use "injection dependency" to solve problem object instance
Examining a mocked object
$transaction = $this->createMock(\AuthorizeNetAIM::class);
print_r(get_class_methods($authorizeNet));
var_dump($authorizeNet->authorizeAndCapture()); -> all method return null
$transaction = $this->createMock(\AuthorizeNetAIM::class);
$response = $this->createMock(\AuthorizeNetAIM_Response::class);
$response->approved = true;
$response->transaction_id = 123;
$transaction->expects($this->once())
->method('authorizeAndCapture')
->willReturn($response);