rawr/phpunit-data-provider

Lightweight builder for PhpUnit data providers

Fund package maintenance!
danon

3.3.0 2023-11-18 16:20 UTC

This package is auto-updated.

Last update: 2024-03-24 12:05:30 UTC


README

t.regx.png

Helper for PhpUnit @dataProvider

Handy require-dev testing tool for PhpUnit. It allows to manage data providers with zip(), join(), cross(), pairs(), slice(), map() and more.

Build Status Coverage Status Dependencies License Composer lock

PHP Version PHP Version PHP Version PHP Version PHP Version PHP Version PHP Version

PRs Welcome

  1. Installation
  2. Overview
  3. Documentation
  4. Migration

Installation

Installation for PHP 7.1 and later:

composer require --dev rawr/phpunit-data-provider

Overview

DataProvider can be used to build, compose and edit data providers to be used with PhpUnit by @sebastianbergmann.

DataProvider::list()

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');
}

list.png

Additionally, DataProvider::list() names rows based on values.

DataProvider::join()

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']
  ];
}

join.png

Note:

  • Only data provider with equal amounts of arguments in rows can be joined. DataProvider.drop() can be used to trim overflowing columns, or DataProvider::zip() to widen data provider with less rows.

DataProvider::zip()

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()];
}

zip.png

Note:

  • Only data provider with equal amounts of rows can be zipped. DataProvider.slice() can be used to trim overflowing rows, or DataProvider::join() to prolong a shorter data provider.

DataProvider::cross()

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');
}

cross.png

DataProvider::pairs()

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');
}

pairs.png

DataProvider::of()

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']
  ];
}

of.png

Notes:

DataProvider::tuples()

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']
  );
}

tuples.png

DataProvider::dictionary()

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'
  ]);
}

dictionary.png

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  
  ]);
}

DataProvider.map()

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']);
}

map.png

Notes:

  • Names in DataProvider will be preserved.

DataProvider.slice()

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']
  );
}

DataProvider.entries()

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',
  );
}

entries.png

Documentation

Functionalities

Features

  • Clear naming
    • Each DataProvider builder sets proper names for rows, based on values.
  • 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.
  • DataProvider accepts many provider types.

Names

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']
  );
}

names names

Example usage

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]));
}

Advanced usage

DataProvider can be combined with other DataProviders 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']
   ];
}

Accepted types

DataProvider accepts any type of data provider:

Notes

Notes on DataProvider::join():

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 providers
  • DataProvider::zip() can only zip data providers with the same amount of rows, otherwise IrregularDataProviderException is thrown. Additionally, each particular data provider must have the same amount of arguments in each row.
  • DataProvider::zip() accepts DataProvider or other iterable 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'), while DataProvider::distinctPairs() only makes pairs of different arguments.

Note on DataProvider::tuples():

  • DataProvider::tuples() is similar to DataProvider::of(), but ::of() accepts an explicit name, while DataProvider::tuples() titles the rows according to the values in the row.

Migration from previous version

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 to iterable or \TRegx\PhpUnit\DataProviders\DataProvider.
  • Removed \TRegx\DataProvider\CrossDataProviders::builder(), use \TRegx\PhpUnit\DataProviders\DataProvider::cross() instead.