Coder Social home page Coder Social logo

olvlvl / phpunit-given Goto Github PK

View Code? Open in Web Editor NEW
1.0 1.0 1.0 15 KB

An alternative to PHPUnit's ReturnValueMap and ReturnCallback. A convenient solution to migrate from Prophecy.

License: Other

Dockerfile 4.44% Makefile 5.25% PHP 90.32%
php phpunit test-doubles

phpunit-given's Introduction

olvlvl/phpunit-given

Packagist Code Quality Code Coverage Downloads

olvlvl/phpunit-given provides an alternative to PHPUnit's ReturnValueMap and ReturnCallback, as well as a convenient solution to migrate from Prophecy.

Disclaimer

In most cases ReturnCallback with match can be used effectively. Don't use this package if you're comfortable with these and don't need extra features.

Usage

This is a simple example, more use cases below.

use olvlvl\Given\GivenTrait;
use PHPUnit\Framework\TestCase;

final class IntegerNameTest extends TestCase
{
    use GivenTrait; // <-- adds the method 'given'

    public function testName(): void
    {
        $mock = $this->createMock(IntegerName::class);
        $mock->method('name')->will($this
            ->given(new Integer(6))->return("six")
            ->given(new Integer(12))->return("twelve")
            ->default()->throw(LogicException::class)
        );

        $this->assertEquals("six", $mock->name(new Integer(6)));
        $this->assertEquals("twelve", $mock->name(new Integer(12)));

        $this->expectException(LogicException::class);
        $mock->name(new Integer(99));
    }
}

Installation

composer require olvlvl/phpunit-given

Motivation

Coming from Prophecy, C# Moq, Golang Mock, or Kotlin Mockk, one would expect at least one of the following examples to work, but they do not.

$mock = $this->createMock(IntegerName::class);
$mock
    ->method('name')
    ->with(new Integer(6))
    ->willReturn("six");
$mock
    ->method('name')
    ->with(new Integer(12))
    ->willReturn("twelve");

// the next line crashes with: Expectation failed
$this->assertEquals("six", $mock->name(new Integer(6)));
$mock = $this->createMock(IntegerName::class);
$mock
    ->method('name')
    ->with(new Integer(6))->willReturn("six");
    // the next line crashes with: Method parameters already configured
    ->with(new Integer(12))->willReturn("twelve");

$this->assertEquals("six", $mock->name(new Integer(6)));

To return a value given certain arguments, one is expected to use ReturnValueMap or ReturnCallback. ReturnValueMap seems simple enough, but because it looks for exact matches it fails when objects are included in the arguments, unless they are the same instances. Besides, ReturnValueMap does not support constraints, you can forget doing anything fancy with it. That leaves us with ReturnCallback, which can be used effectively with match but requires the introduction of logic in the test, a practice that is discouraged.

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->willReturnCallback(fn (Integer $int) => match ($int) {
    new Integer(6) => 'six',
    new Integer(12) => 'twelve',
    default => throw new Exception
}));

My motivation creating olvlvl/phpunit-given, is to have an alternative to ReturnValueMap and ReturnCallback, that looks similar to what we find in other testing frameworks, and that allows easy migration from Prophecy.

Some PHPUnit issues, for reference:

Use cases

Comparing objects

ReturnValueMap doesn't work with objects because it uses strict equality when comparing arguments. The following code throws a TypeError exception because ReturnValueMap cannot find a match and defaults to a null value.

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->will($this->returnValueMap([
    [ new Integer(6), "six" ],
    [ new Integer(12), "twelve" ],
]));

$mock->name(new Integer(6)); // throws TypeError

olvlvl/phpunit-given substitutes values with Assert::equalTo() and compares arguments using constraints. Having objects in the arguments is not a problem.

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->will($this
    ->given(new Integer(6))->return("six")
    ->given(new Integer(12))->return("twelve")
);

$this->assertEquals("six", $mock->name(new Integer(6)));
$this->assertEquals("twelve", $mock->name(new Integer(12)));

Note: You can use Assert::identicalTo() to check for the same instance.

Using constraints

We established that values are substituted with Assert::equalTo() internally. Instead of values, you can also use constraints:

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->will($this
    ->given(Assert::lessThan(new Integer(6)))->return('too small')
    ->given(Assert::greaterThan(new Integer(9)))->return('too big')
    ->default()->return('just right') // `default()` is a shortcut for `given(Assert::anything())`
);

$this->assertEquals("too small", $mock->name(new Integer(5)));
$this->assertEquals("too big", $mock->name(new Integer(10)));
$this->assertEquals("just right", $mock->name(new Integer(6)));
$this->assertEquals("just right", $mock->name(new Integer(9)));

Of course, you could use ReturnCallback, although it adds logic to the test. Use whatever you feel more comfortable with.

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->willReturnCallback(fn (Integer $int) => match (true) {
    $int < new Integer(6) => 'too small',
    $int > new Integer(9) => 'too big',
    default => 'just right';
}));

Migrating from Prophecy

olvlvl/phpunit-given is a convenient solution to migrate from Prophecy because the code is quite similar:

$container = $this->prophesize(ContainerInterface::class);
$container->has('serviceA')->willReturn(true);
$container->has('serviceB')->willReturn(false);
$container = $this->createMock(ContainerInterface::class);
$container->method('has')->will($this
    ->given('serviceA')->return(true)
    ->given('serviceB')->return(false)
);

throw() is an alternative to willThrow(), and you can mismatch return() and throw():

$container = $this->prophesize(ContainerInterface::class);
$container->get('serviceA')->willReturn($serviceA);
$container->get('serviceB')->willThrow(new LogicException());
$container = $this->createMock(ContainerInterface::class);
$container->method('get')->will($this
    ->given('serviceA')->return($serviceA)
    ->given('serviceB')->throw(LogicException::class)
);

Contrary to Prophecy, olvlvl/phpunit-given does not return null by default, instead it throws an exception:

$mock = $this->createMock(IntegerName::class);
$mock->method('name')->will($this
    ->given(new Integer(6))->return("six")
    ->given(new Integer(12))->return("twelve")
);

$mock->name(new Integer(13)); // throws an exception
LogicException : Unexpected invocation: Test\olvlvl\Given\Acme\IntegerName::name(Test\olvlvl\Given\Acme\Integer Object (...)): string, didn't match any of the constraints: [ [ is equal to Test\olvlvl\Given\Acme\Integer Object &000000000000000c0000000000000000 (
'value' => 6
) ], [ is equal to Test\olvlvl\Given\Acme\Integer Object &00000000000001af0000000000000000 (
'value' => 12
) ] ]

Continuous Integration

The project is continuously tested by GitHub actions.

Tests Static Analysis Code Style

Code of Conduct

This project adheres to a Contributor Code of Conduct. By participating in this project and its community, you are expected to uphold this code.

Contributing

Please see CONTRIBUTING for details.

License

olvlvl/phpunit-given is released under the BSD-3-Clause.

phpunit-given's People

Contributors

olvlvl avatar

Stargazers

 avatar

Watchers

 avatar

Forkers

net-tools

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.