Given you have (possibly legacy) code,
and you want to cover it with regression tests,
and you don't want to specify the output for each input in your unit tests,
and you don't want to adjust all the tested output after you changed the logic,
then you should use Approval Tests.
composer require ahj/approval-tests --dev
Find an example on how to do Approval Testing under /tests/Example.
Create a test with phpunit, specify the input for your logic, store the output in a
variable and pass it into
Approvals::create()->verifyList($input, $output)
:
public function testUpdateQuality(): void
{
$input = [
new Item('foo', 0, 1),
];
$output = (new GildedRose())->updateQuality($input);
Approvals::create()->verifyList($input, $output);
}
verifyList($input, $output)
will create a map of the received data and compare it to
the previous version of this data by performing an Assert::assertEquals($approved, $received)
.
Then run phpunit:
$ vendor/bin/phpunit tests
Then you will see a new directory in your test directory that contains two files:
/approval
|__ approved.txt
|__ received.txt
The approved.txt
is initially empty.
In the received.txt
you will find the $input
mapped to the $output
such as:
[foo, 0, 1] -> [foo, -1, 0]
Approve the $output
by copying the content of received.txt
to approved.txt
or use the command from your console:
$ mv tests/Example/approval/received.txt tests/Example/approval/approved.txt
When you run your test again, the received.txt
will be gone, and you will have your test output in the approval.txt
.
Next you will just add more cases to your $input
array in your test and approve the results.
No need to specify any output manually :)
Pass an empty array as $input
if your logic doesn't require input:
Approvals::create()->verifyList([], $output);
In the received.txt
you will only show the $output
such as:
[foo, -1, 0]
Pass $plain = true
as third argument to verifyList()
in order to have
non formated output in the received.txt
:
Approvals::create()->verifyList([], $output, true);
Depending on the type of $output
there will either be a plain or json encoded string in the received.txt
.
For objects or arrays it will be:
{"dirPath":"bar","fileNumber":10,"randomList":[]},{"dirPath":"bar","fileNumber":100,"randomList":[]}
For a string it will be:
+--------------+-------------+--------+--------+------+-----+------------+---------+---------+---------+---------+---------+--------+-------+
| benchmark | subject | groups | params | revs | its | mem_peak | best | mean | mode | worst | stdev | rstdev | diff |
+--------------+-------------+--------+--------+------+-----+------------+---------+---------+---------+---------+---------+--------+-------+
| HashingBench | benchMd5 | | [] | 1000 | 10 | 1,255,792b | 0.931μs | 0.979μs | 0.957μs | 1.153μs | 0.062μs | 6.37% | 1.00x |
| HashingBench | benchSha1 | | [] | 1000 | 10 | 1,255,792b | 0.988μs | 1.015μs | 1.004μs | 1.079μs | 0.026μs | 2.57% | 1.04x |
+--------------+-------------+--------+--------+------+-----+------------+---------+---------+---------+---------+---------+--------+-------+
You have a function that takes, for example, three arguments, and you want to test its behaviour with a bunch of different values for each of those arguments.
Copy the below code or use this repo's example
and adjust the number and type of inputs for your use case.
Specify the input arguments of the method you want to test in $arguments
:
Either list the values that the arguments can take explicitly in arrays
or use range()
.
Then pass those arguments along with an anonymous function into
CombinationApprovals::create()->verifyAllCombinations()
public function testUpdateQualityWithCombinations(): void
{
$arguments = [
['foo', 'bar'], # values for $name
range(0, 3), # values for $sellIn
[15, 20, 25], # values for $quantity
];
CombinationApprovals::create()->verifyAllCombinations(
function (string $name, int $sellIn, int $quantity) {
$items = [new Item($name, $sellIn, $quantity)];
return (new GildedRose())->updateQuality($items);
},
$arguments
);
}
The anonymous function specifies how the input arguments should be passed
into the logic that you want to test. Also, it must return the output of
your tested logic, so verifyAllCombinations()
can dump it into
the received.txt and compare it to the latest approved.txt.
For the above example, the Approval tool would create all possible combinations of the specified input values, map those to the related output of the tested logic and dump it into the received.txt as such:
[bar, 3, 25] -> [bar, 2, 24]
[bar, 3, 20] -> [bar, 2, 19]
[bar, 3, 15] -> [bar, 2, 14]
[bar, 2, 25] -> [bar, 1, 24]
[bar, 2, 20] -> [bar, 1, 19]
[bar, 2, 15] -> [bar, 1, 14]
[bar, 1, 25] -> [bar, 0, 24]
[bar, 1, 20] -> [bar, 0, 19]
[bar, 1, 15] -> [bar, 0, 14]
[bar, 0, 25] -> [bar, -1, 23]
[bar, 0, 20] -> [bar, -1, 18]
[bar, 0, 15] -> [bar, -1, 13]
[foo, 3, 25] -> [foo, 2, 24]
[foo, 3, 20] -> [foo, 2, 19]
[foo, 3, 15] -> [foo, 2, 14]
[foo, 2, 25] -> [foo, 1, 24]
[foo, 2, 20] -> [foo, 1, 19]
[foo, 2, 15] -> [foo, 1, 14]
[foo, 1, 25] -> [foo, 0, 24]
[foo, 1, 20] -> [foo, 0, 19]
[foo, 1, 15] -> [foo, 0, 14]
[foo, 0, 25] -> [foo, -1, 23]
[foo, 0, 20] -> [foo, -1, 18]
[foo, 0, 15] -> [foo, -1, 13]