Handy require-dev
testing tool for PhpUnit. It allows to manage data providers
with zip()
, join()
, cross()
, pairs()
, slice()
, map()
and more.
Installation for PHP 7.1 and later:
composer require --dev rawr/phpunit-data-provider
DataProvider
can be used to build, compose and edit data providers to be used with PhpUnit
by @sebastianbergmann.
DataProvider::list()
provides a list of elements. Test is invoked each time with a single argument.
/**
* @test
* @dataProvider colors
*/
public function test(string $color): void {
// your test here
}
public function colors(): DataProvider {
return DataProvider::list('blue', 'yellow', 'red');
}
Additionally, DataProvider::list()
names rows based on values.
Vertically join data providers together.
💡 Useful when two data providers are used in other tests, and a new test should use both of them.
/**
* @test
* @dataProvider colors
*/
public function test(string $color, string $thing): void {
// your test here
}
public function colors(): DataProvider {
return DataProvider::join($this->blueColors(), $this->yellowColors(), $this->redColors());
}
public function blueColors(): DataProvider {
return DataProvider::tuples(
['blue', 'sky'],
['deep blue', 'ocean']
);
}
public function yellowColors(): iterable {
yield 'sun' => ['yellow', 'sun'];
}
public function redColors(): array {
return [
'apple' => ['red', 'apple']
];
}
Note:
- Only data provider with equal amounts of arguments in rows can be joined.
DataProvider.drop()
can be used to trim overflowing columns, orDataProvider::zip()
to widen data provider with less rows.
Horizontally join data providers together.
💡 Useful for keeping data providers clean and simple.
/**
* @test
* @dataProvider colors
*/
public function test($blueColor, $blueThing, $adjective, Factory $factory): void {
// your test here
}
public function colors(): DataProvider {
return DataProvider::zip($this->blueThings(), $this->adjectives(), $this->factories());
}
public function blueThings(): DataProvider {
return DataProvider::tuples(
['blue', 'ink'],
['light blue', 'shirt'],
['deep blue', 'ocean']
);
}
public function adjectives(): iterable {
return DataProvider::list('liquid', 'comfortable', 'majestic');
}
public function factories(): iterable {
yield [new InkFactory()];
yield [new ShirtFactory()];
yield [new OceanFactory()];
}
Note:
- Only data provider with equal amounts of rows can be zipped.
DataProvider.slice()
can be used to trim overflowing rows, orDataProvider::join()
to prolong a shorter data provider.
Creates a square matrix of given data providers.
💡 Useful for testing all combinations of arguments.
/**
* @test
* @dataProvider colorsAndThings
*/
public function test(string $color, string $shade): void {
// your test here
}
public function colorsAndThings(): DataProvider {
return DataProvider::cross($this->colors(), $this->things());
}
public function colors(): array {
return [
['blue'], ['yellow'], ['red']
];
}
public function things(): iterable {
return DataProvider::list('sky', 'sun', 'apple');
}
Calls test with two arguments. Each argument is paired with all of the other arguments. All rows are named according to the arguments.
Example shows a test paring image formats:
/**
* @test
* @dataProvider formats
*/
public function shouldConvertFile(string $from, string $to): void {
// your test here
}
public function formats(): array {
return DataProviders::distinctPairs('png', 'jpg', 'bmp');
}
Instantiates a DataProvider
from a raw-array accepted by PhpUnit.
public function example(): DataProvider {
return DataProvider::of($this->rawArray());
}
public function rawArrayDataProvider(): array {
return [
'key' => ['argument 1', 'argument 2']
];
}
Notes:
- Also accepts
iterable
,\Generator
and other types accepted by PhpUnit.
Provide multiple arguments for each a test. DataProvider::tuples()
names each row based on the values.
/**
* @test
* @dataProvider colors
*/
public function test(string $color, string $thing): void {
// your test here
}
public function colors(): DataProvider {
return DataProvider::tuples(
['blue', 'sky'],
['yellow', 'sun'],
['red', 'apple']
);
}
Specify a single argument for test. DataProvider::dictionary()
names each row based on the
provided array key.
/**
* @test
* @dataProvider colors
*/
public function test(string $color): void {
// your test here
}
public function colors(): DataProvider {
return DataProvider::dictionary([
'custom name 1' => 'blue',
'custom name 2' => 'yellow',
'custom name 3' => 'red'
]);
}
In most cases, DataProvider::list()
or DataProvider::tuples()
should be used to name
rows based on arguments. Method DataProvider::dictionary()
is useful when the arguments are not self-explanatory:
public function ports(): DataProvider {
return DataProvider::dictionary([
'http' => 80,
'https' => 443,
'ftp' => 21
]);
}
Transform each row's values in DataProvider
to any other set of values.
💡 Useful for separating providers content and their form.
/**
* @test
* @dataProvider folderIterators
*/
public function test(\Iterator $iterator, string $name): void {
// your test here
}
public function folderIterators(): DataProvider {
return $this->folders()->map(function (string $name, string $path): array {
return [
new \DirectoryIterator($path),
$name
];
});
}
public function folders(): DataProvider {
return DataProvider::tuples(
['temporary', '/tmp'],
['user directory', '/home'],
['system resources', '/usr/bin']);
}
Notes:
- Names in
DataProvider
will be preserved.
Remove leading or trailing rows from DataProvider
.
💡 Useful for adapting DataProvider
to be zipped or limit provided values.
/**
* @test
* @dataProvider limitedColors
*/
public function test(string $color, string $thing): void {
// your test here
}
public function limitedColors(): DataProvider {
return $this->colors()->slice(0, 2);
}
public function colors(): DataProvider {
return DataProvider::tuples(
['blue', 'sky'],
['yellow', 'sun'],
['red', 'apple']
);
}
Provide two arguments for each a test, from key-value pairs.
DataProvider::entries()
names each row based on the key-value pair.
/**
* @test
* @dataProvider colors
*/
public function test(string $color, string $thing): void {
// your test here
}
public function colors(): DataProvider {
return DataProvider::entries(
'blue' => 'sky',
'yellow' => 'sun',
'red' => 'apple',
);
}
-
Creating new data providers:
-
Composing existing providers:
-
Editing existing providers:
DataProvider.map()
,DataProvider.slice()
,DataProvider.drop()
. These methods don't modifyDataProvider
instance, but return a new instance.
- Clear naming
- Each
DataProvider
builder sets proper names for rows, based on values.
- Each
- Duplicate keys:
- Duplicate keys are properly handled and formatted in an informative manner. No rows are ever being "lost" when editing.
- Lazy evaluation:
- Iterators are being evaluated only when called. Argument iterators are only called once, even if
DataProvider
is called multiple times.
- Iterators are being evaluated only when called. Argument iterators are only called once, even if
DataProvider
accepts many provider types.
DataProvider
sets proper names for each row based on values.
/**
* @test
* @dataProvider colors
*/
public function test(string $color, string $thing): void {
// your test here
}
public function colors(): DataProvider {
return DataProvider::tuples(
['blue', 'sky'],
['yellow', 'sun'],
['red', 'apple']
);
}
DataProvider::cross()
returns an instance of DataProvider
which is a square matrix of input data providers.
/**
* @test
* @dataProvider services
*/
public function shouldLogin(string $service, string $method, int $port): void {
// your test here
}
public function services(): DataProvider {
return DataProvider::cross(
[
['github.com'], ['bitbucket.com'], ['gitlab.com'], ['sourceforge.net']
],
[
['http', 80],
['https', 443],
['ssh', 22]
]);
}
This is equivalent of having a regular data provider that is composed of 12 entries, similar to:
public function services(): array {
return [
['github.com', 'http', 80],
['github.com', 'https', 443],
['github.com', 'ssh', 22],
['bitbucket.com', 'http', 80],
['bitbucket.com', 'https', 443],
['bitbucket.com', 'ssh', 22],
['gitlab.com', 'http', 80],
['gitlab.com', 'https', 443],
['gitlab.com', 'ssh', 22],
['sourceforge.net', 'http', 80],
['sourceforge.net', 'https', 443],
['sourceforge.net', 'ssh', 22],
];
}
DataProvider::cross()
accepts data providers of different
types: array
, \Iterator
, \IteratorAggregate
, \Traversable
, \Generator
,
iterable
and DataProvider
.
That means DataProvider
can be composed together.
public function services(): DataProvider {
return DataProvider::cross(
DataProvider::list('github.com', 'bitbucket.com', 'gitlab.com', 'sourceforge.net'),
DataProvider::tuples(['http', 80], ['https', 443], ['ssh', 22]));
}
DataProvider
can be combined with other DataProvider
s as well as regular PhpUnit data providers.
/**
* @test
* @dataProvider urls
*/
public function test0(string $url): void {
// your test here
}
/**
* @test
* @dataProvider services
*/
public function test1(string $url, string $name, string $method, int $port): void {
// your test here
}
/**
* @test
* @dataProvider allServices
*/
public function test2(string $url, string $name, string $method, int $port): void {
// your test here
}
public function urls(): DataProvider {
return DataProvider::list('github.com', 'bitbucket.com', 'gitlab.com', 'sourceforge.net');
}
public function rawArrayProvider(): array {
return [
['GitHub'],
['BitBucket'],
['GitLab'],
['SourceForge']
];
}
public function services(): DataProvider {
return DataProvider::cross(
DataProvider::zip($this->urls(), $this->rawArrayProvider()),
DataProvider::tuples(
['http', 80],
['https', 443],
['ssh', 22]));
}
public function allServices(): DataProvider {
return DataProvider::join(
$this->services(),
$this->localServices()
);
}
public function localServices(): array {
return [
'my local service' => ['localhost', 'local', 'http', '80']
];
}
DataProvider
accepts any type of data provider:
- all types allowed
by PhpUnit:
array
,iterable
,\Traversable
,\Iterator
,\IteratorAggregate
,\Generator
DataProvider
itself, allowing data providers to be composed together
Notes on DataProvider::join()
:
DataProvider::join()
preserves names of each data provider, and also joins the names vertically. Duplicates in titles are preserved and presented appropriately.DataProvider::join()
accepts any type of data-provider.DataProvider::join()
is conceptually similar to calling\array_merge()
on raw-array providers, but\array_merge()
would override duplicate keys, whileDataProvider::join()
preserves duplicate keys, and titles them appropriately.DataProvider::join()
variadic arguments...iterable
and can be used to join many data providersDataProvider::join()
can only join data providers with the same amount of arguments in each row, otherwiseIrregularDataProviderException
is thrown.DataProvider::join()
acceptsDataProvider
or otheriterable
accepted by PhpUnit. If improper data-provider is passed,MalformedDataProviderException
is thrown.
Notes on DataProvider::zip()
:
DataProvider::zip()
preserves names of each data provider, and also joins them horizontally.DataProvider::zip()
accepts any type of data-provider.DataProvider::zip()
variadic arguments...iterable
and can zip many data providersDataProvider::zip()
can only zip data providers with the same amount of rows, otherwiseIrregularDataProviderException
is thrown. Additionally, each particular data provider must have the same amount of arguments in each row.DataProvider::zip()
acceptsDataProvider
or otheriterable
accepted by PhpUnit. If improper data-provider is passed,MalformedDataProviderException
is thrown.
Note on DataProvider::pairs()
:
DataProvider::pairs()
produces duplicate pairs (for example'png', 'png'
), whileDataProvider::distinctPairs()
only makes pairs of different arguments.
Note on DataProvider::tuples()
:
DataProvider::tuples()
is similar toDataProvider::of()
, but::of()
accepts an explicit name, whileDataProvider::tuples()
titles the rows according to the values in the row.
To use version 3.0.0
, migrating from 2.4.0
or earlier:
- Library namespace changed from
\TRegx\DataProvider\
to\TRegx\PhpUnit\DataProviders\
. - Change
\TRegx\DataProvider\DataProviders::cross()
to\TRegx\PhpUnit\DataProviders\DataProvider::cross()
. - Change
\TRegx\DataProvider\CrossDataProviders::cross()
to\TRegx\PhpUnit\DataProviders\DataProvider::cross()
. - Change your data providers return type from
array
toiterable
or\TRegx\PhpUnit\DataProviders\DataProvider
. - Removed
\TRegx\DataProvider\CrossDataProviders::builder()
, use\TRegx\PhpUnit\DataProviders\DataProvider::cross()
instead.