rawr/cross-data-providers

Allows you to use mix multiple PhpUnit @dataProvider without duplication

2.1.0 2020-01-01 19:34 UTC

This package is auto-updated.

Last update: 2020-03-24 10:39:22 UTC


README

t.regx.png

Cross @dataProviders

Handy require-dev testing tool for PhpUnit.

It allows you to create square matrices of your data providers!

Build Status Coverage Status Dependencies Repository Size License Composer lock

PHP Version PHP Version PHP Version PHP Version PHP Version

PRs Welcome

  1. Installation
  2. Examples
  3. Pairs

Installation

Installation for PHP 7.0 and later:

$ composer require --dev rawr/cross-data-providers

Examples

Ever wanted to use multiple PhpUnit @dataProvider's with each other? Well, look no more :)

Imagine you have a service that allows you to log in to GitHub, BitBucket, SourceForge and GitLab with either SSH, HTTP or HTTPS and you want to test each possible configuration of those.

/**
 * @test
 * @dataProvider services
 */
public function shouldLogin(string $service, string $method, int $port) {
    // given
    $login = new Login($method, $port);
    
    // when
    $result = $login->log($service);
    
    // then
    $this->assertTrue($result);
}

function services() {
    return DataProviders::cross(
      [
        ['github.com'],
        ['bitbucket.com'],
        ['gitlab.com'],
        ['sourceforge.net']
      ],
      [
        ['http', 80],
        ['https', 443],
        ['ssh', 22]
      ]
    );
}

This is equivalent of having a regular dataProvider that is composed of 12 entries, that look like this:

function services() {
    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],
    ];
}

More advanced example

Let's say that apart from the domain and the protocol, you'd also like to add the protocol port, and the service title. Further more, you'd like to have three strategies of connection: lazy, eager and a test dry run.

/**
 * @test
 * @dataProvider services
 */
public function shouldLogin(string $service, string $title, string $method, int $port, $strategy) {
    // given
    $login = new Login($method, $port);
    $login->useStrategy($strategy);
    
    // when
    $result = $login->log($service);
    
    // then
    $this->assertTrue($result, "Failed to login to $title");
}

function services() {
    return DataProviders::cross(
      [
        // First section (two paramters): $service and $title
        ['github.com',        'GitHub'],
        ['bitbucket.com',     'BitBucket'],
        ['gitlab.com',        'GitLab'],
        ['sourceforge.net',   'SourceForge'],
        ['www.gitkraken.com', 'Git Kraken']
      ],
      [
        // Second section: (pair of parameters): $method and $port
        ['http',  80],
        ['https', 443],
        ['ssh',   22]
      ],
      [
        // Last section: single parameter of $strategy
        new EagerStrategy(),
        new LazyStrategy(),
        new DryRunStrategy(),
      ]
    );
}

This is equal to a @dataProvider with 45 entries. The test will be run 45 times, each time with a unique combination of your sections :)

DataProvider builder

The above example with DataProviders::cross(), can also be written with a help of a builder:

function services() {
    return DataProviders::builder()
        ->addJoiningSection(
            ['github.com',        'GitHub'],
            ['bitbucket.com',     'BitBucket'],
            ['gitlab.com',        'GitLab'],
            ['sourceforge.net',   'SourceForge'],
            ['www.gitkraken.com', 'Git Kraken'])
        ->addJoiningSection(
            ['http',  80],
            ['https', 443],
            ['ssh',   22])
        ->addSection(
            new EagerStrategy(),
            new LazyStrategy(),
            new DryRunStrategy())
        ->build();
}
  • addSection() simply adds another iteration of parameters. This is usually the default behaviour, since joined sections appear quite rarely in most applications.
  • addJoiningSection() allows for specifying multiple arguments for your PhpUnit method, that are supposed to be present together, like address and title or a protocol and port.

The most common down-to-earth example could look similar to this:

/**
 * @test
 * @dataProvider trimMethods
 */
public function shouldTrimWhitespace(string $strategy, string $whitespace) {
    // given
    $remover = new WhitespaceRemover($strategy);

    // when
    $result = $remover->remove($whitespace);

    // then
    $this->assertEmpty($result);
}

function trimMethods() {
    return DataProviders::builder()
        ->addSection('trim', 'preg_replace', 'str_replace', 'mb_str_replace')
        ->addSection(' ', '\t', '\n', '\r', PHP_EOL)
        ->build();
}

Pairs

Sometimes, a square matrix of data providers is not what we need. Sometimes a more overall solution is need. What if instead of http/https and github/gitlab/bitbucket/sourceforge that needed to be tested together, there were file formats to be tested.

Let's say your brand new FileConverter::convert() method must convert any image type to any other image type, including the same type again:

/**
 * @test
 * @dataProvider formats
 */
public function shouldConvertFile(string $from, string $to) {
    // given
    $converter = new FileConverter();

    // when
    $result = $converter->convert($from, $to);

    // then
    $this->assertEquals($to, FormatUtils::detectFormat($result));
}

function formats() {
    return DataProviders::pairs('png', 'bmp', 'jpg', 'gif');
}

And that's it! It will mix and match every format ('png', 'bmp', 'jpg', 'gif') and create pairs of each of those.

It is an equivalent of 16 data providers:

  • ['png', 'png'], ['png', 'bmp'], ['png', 'jpg'], ['png', 'gif'],
  • ['bmp', 'png'], ['bmp', 'bmp'], ['bmp', 'jpg'], etc.

If you would like to ignore duplicate pairs (so to never convert from png to png), use DataProviders::distinctPairs().

Currently, there is no key-mapper for DataProviders::pairs(), but please, create an issue for a key-mapper and I'll create it shortly :)