Open menu icon N-ARY CIRCLED PLUS OPERATOR

atoum/visibility-extension example workflow name

The atoum visibility-extension allows you to override method visibility in your unit tests. For example, you will be able to test protected method with it.

Example

In the example, we test the protected method bar:

<?php

namespace
{
  class foo
  {
    protected function bar()
    {
      return 'foo';
    }
  }
}

namespace tests\units
{
  use atoum\atoum;

  class foo extends atoum\test
  {
    public function testBar()
    {
      $this
        ->if($sut = new \foo())
        ->then
          ->string($this->invoke($sut)->bar())->isEqualTo('foo')
      ;
    }
  }
}

Install it

Install extension using composer:

composer require --dev atoum/visibility-extension

The extension will be automatically loaded. If you ever want to unload it, you can add this to your configuration file:

<?php

// .atoum.php

use atoum\atoum\visibility;

$runner->removeExtension(visibility\extension::class);

Use it

You can achieve visibility override using two methods:

Override concrete classes' methods visibility

Overriding methods visibility is done on-the-fly in unit tests body using the invoke method:

<?php

namespace
{
    class foo
    {
        protected function bar()
        {
            return $this;
        }

        protected function baz($arg)
        {
            return $arg;
        }
    }
}

namespace tests\units
{
    use atoum\atoum;

    class foo extends atoum\test
    {
        public function testBar()
        {
            $this
                ->if($sut = new \foo())
                ->then
                    ->object($this->invoke($sut)->bar())->isIdenticalTo($sut)
            ;
        }

        public function testBaz()
        {
            $this
                ->if($sut = new \foo())
                ->and($arg = uniqid())
                ->then
                    ->variable($this->invoke($sut)->baz($arg))->isEqualTo($arg)
            ;
        }
    }
}

As you can see, we only used the invoke method. It has a special syntax that we are going to detail: $this->invoke(<object instance>)-><method name>(<arguments>)

Override mocked classes' methods visibility

Overriding mocked classes' methods requires a bit more work, involving the mock generator. Before detailing how to achieve that, keep in mind that there are some limitations you have to be aware of. We'll detail the just after a short example:

<?php

namespace
{
    class foo
    {
        public function baz()
        {
            return $this->bar();
        }

        protected function bar()
        {
            $args = func_get_args();

            return sizeof($args) ? $args : $this;
        }
    }
}

namespace tests\units
{
    use atoum\atoum;

    class foo extends atoum\test
    {
        public function testBar()
        {
            $this
                ->given(
                    $this->mockGenerator
                        ->makeVisible('bar')
                        ->generate('foo')
                )
                ->if($mockedSut = new \mock\foo)
                ->and($this->calling($mockedSut)->bar = 'foo')
                ->then
                    ->string($mockedSut->baz())->isEqualTo('foo')
                    ->string($mockedSut->baz())->isEqualTo('foo')
                    ->mock($mockedSut)
                        ->call('bar')->twice()
            ;
        }
    }
}

The mock generator now provides a makeVisible method which you can call to override a method visibility. You have to call this method before the generation of the mocked class which happens the first time a mock is instanciated or when you call the generate method of the mock controller.

Doing this will create a child class (the mock) and define the protected methods as public. You will then be able to call them directly, without even using the invoke method we saw in the previous section.

You will also be able to assert on those methods' calls using standard atoum assertions.

Now let's talk about the limitations:

When you want to temporarily override the visibility of a mock class method, you can change the name of the mocked class using the generate method's arguments. Using the previous example, it would look like:

<?php

namespace tests\units
{
    use atoum\atoum;

    class foo extends atoum\test
    {
        public function testBar()
        {
            $this
                ->given(
                    $this->mockGenerator
                        ->makeVisible('bar')
                        ->generate('foo', 'mock', 'mockedFoo')
                )
                ->if($mockedSut = new \mock\mockedFoo)
                ->and($this->calling($mockedSut)->bar = 'foo')
                ->then
                    ->string($mockedSut->baz())->isEqualTo('foo')
                    ->string($mockedSut->baz())->isEqualTo('foo')
                    ->mock($mockedSut)
                        ->call('bar')->twice()
            ;
        }
    }
}

Doing this, we would generate a \mock\mockedFoo class from the \foo class with a looser visiblity on the bar method. This will allow us to bypass some limitation:

Links

License

visibility-extension is released under the BSD-3 Clause License. See the bundled LICENSE file for details.

atoum