gremo
gremo

Reputation: 48899

Testing that no methods (whatever the name) will be invoked in PHPUnit?

Part of the test subject:

class AddOptionsProviderArgumentPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if(!$container->hasDefinition('gremo_highcharts')) {
            return;
        }

        if(!$container->hasParameter('gremo_highcharts.options_provider')) {
            return;
        }

        // ...
    }
}

I want to assert that:

One solution would be asserting about the subsequent call to hasParameter():

public function testProcessWillReturnIfThereIsNoServiceDefinition()
{
    $container = $this->getMockedContainerBuilder();
    $pass = new AddOptionsProviderArgumentPass();

    $container->expects($this->once())
        ->method('hasDefinition')
        ->with($this->equalTo('gremo_highcharts'))
        ->will($this->returnValue(false));

    // Expects that hasParameter() is never invoked
    $container->expects($this->never())
        ->method('hasParameter');

    $pass->process($container);
}

But it doesn't seem an elegant solution.

Upvotes: 7

Views: 1100

Answers (4)

JustAC0der
JustAC0der

Reputation: 3157

Maybe creating Phake mocks and calling

Phake::verifyNoInteraction($mock);

is a good solution to this problem. Here's a link to the manual: https://phake.readthedocs.io/en/2.1/method-verification.html#verifying-no-interaction-with-a-mock-so-far .

Upvotes: 0

zafarkhaja
zafarkhaja

Reputation: 2582

When testing such methods, try to see the big picture. Don't descend to the level of ifs and returns, take it higher. By asserting that no other call was made after return, what You really test are the PHP's native statements, not the logic of Your method. It's like You don't trust the returns. Take my word for it, after return statement nothing is executed in that method :)

Instead, test the logic of Your method!

What is the logic?

Well, according to Your code, You have this class AddOptionsProviderArgumentPass and its process method. The pocess method takes a ContainerBuilder and processes it somehow. So, what You need to test is that the process method does its job well. Your ifs in the method represent some constraints which need to be satisfied in order to successfully process the ContainerBuilder.

How do You understand if the process was successful?

By its return type.

What if it doesn't return anything?

Check for its side effects. Which are the things You do to the ContainerBuilder.

So, here is how I see it.

/**
 * @test
 */
public function shouldNotProcessWithoutHighcharts()
{
    // Arrange
    $container = $this->buildContainer();
    $container->removeDefinition('gremo_highcharts');
    $pass = new AddOptionsProviderArgumentPass();

    // Act
    $pass->process($container);

    // Assert
    $this->assertFalse($container->hasWhatYouNeedItToHaveAfterProcessing())
}

/**
 * @test
 */
public function shouldNotProcessWithoutHighchartsOptionsProvider()
{
    // Arrange
    $container = $this->buildContainer();
    $container->getParameterBag()->remove('gremo_highcharts.options_provider');
    $pass = new AddOptionsProviderArgumentPass();

    // Act
    $pass->process($container);

    // Assert
    $this->assertFalse($container->hasWhatYouNeedItToHaveAfterProcessing())
}

private function buildContainer()
{
    $container = new ContainerBuilder();
    $container->setParameter('gremo_highcharts.options_provider');
    $container->setDefinition('gremo_highcharts');
    return $container;
}

Last note

Don't rely on the order of ifs, it can change!

Upvotes: 1

edorian
edorian

Reputation: 38961

To express any method you can use $this->anything().

Full example:

<?php

class fooTest extends PHPUnit_Framework_TestCase {
    public function testNeverCallNothing() {
        $mock = $this->getMock('mockMe');
        $mock->expects($this->never())->method($this->anything());
        //$mock->bar();
    }
}

class mockMe {
    public function bar() {}
}

Outputs:

PHPUnit 3.7.10-4-ga0bccf3 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 6.50Mb

OK (1 test, 1 assertion)

When commenting in the method call

$mock->bar();

it then outputs:

PHPUnit 3.7.10-4-ga0bccf3 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 6.50Mb

There was 1 failure:

1) fooTest::testNeverCallNothing
mockMe::bar() was not expected to be called.

.../tests/neverCallMe/fooTest.php:9

FAILURES!
Tests: 1, Assertions: 0, Failures: 1.

Allowing for only one method call but no calls to other methods

This looks a little ugly but also works

<?php

class fooTest extends PHPUnit_Framework_TestCase {

    public function testNeverCallNothing() {
        $mock = $this->getMock('mockMe');
        $mock->expects($this->once())->method('foo');
        $mock->expects($this->never())->method(
            $this->logicalNot($this->matches('foo'))
        );
        $mock->foo();
        //$mock->bar();
    }


}

class mockMe {
    public function bar() {}
    public function foo() {}
}

Works. When commenting in the other method calls it fails like above.

If one wants to allow for multiple methods to be called it gets a little more verbose:

$this->logicalNot(
    $this->logicalOr(
        $this->matches('foo'),
        $this->matches('baz'),
        $this->matches('buz')
    )
)

Upvotes: 3

user217782
user217782

Reputation:

Is it an exceptional case? If so, you could change the first return (why are you returning void anyway?) to throwing a specific exception. Then use PHPUnit to verify that that specific exception is actually caught.

Edit: Also with Phake you could write something like this at the end of your test: (similar to calling ->never() with PHPUnit Mock Objects)

Phake::verify($container, Phake::times(0))->hasParameter();

This creates a distinction between stubbing method calls and verifying that methods (stubbed or not) have been called.

Upvotes: 2

Related Questions