5

I have a trait:

trait A {
    function foo() {
        ...
    }
}

and a class that uses the trait like this:

class B {
    use A {
        foo as traitFoo;
    }

    function foo() {
        $intermediate = $this->traitFoo();
        ...
    }
}

I want to test the class' foo() method and want to mock (with Mockery) the behavior of the trait's foo() method. I tried using a partial and mocking traitFoo() like:

$mock = Mockery::mock(new B());
$mock->shouldReceive('traitFoo')->andReturn($intermediate);

But it doesn't work.

Is it possible to do this? Is there an alternative way? I want to test B::foo() isolating it from the trait's foo() implementation.

Thanks in advance.

Gonçalo Marrafa
  • 2,013
  • 4
  • 27
  • 34
  • How "it doesn't work." exactly? Are you calling it as `$mock->foo()` in the test? Generally speaking, mocking CUT is not really recommended. – Alex Blex Apr 06 '17 at 15:24
  • Yes i'm calling it as `$mock->foo()`. It doesn't work because the real trait method is called and not the mock one. I'm using the mock as a proxy for the CUT and to be able to isolate the method test without relying on the trait, that's tested elsewhere. – Gonçalo Marrafa Apr 06 '17 at 16:33

1 Answers1

6

The proxy mock you are using proxies calls from outside the mock, so internal calls within the class $this->... cannot be mocked.

If you have no final methods, you still can use normal partial mock or a passive partial mock, which extends the mocked class, and don't have such limitations:

$mock = Mockery::mock(B::class)->makePartial();
$mock->shouldReceive('traitFoo')->andReturn($intermediate);
$mock->foo();

UPDATE:

The full example with non-mocked trait functions:

use Mockery as m;

trait A
{
    function foo()
    {
        return 'a';
    }

    function bar()
    {
        return 'd';
    }
}

class B
{
    use A {
        foo as traitFoo;
        bar as traitBar;
    }

    function foo()
    {
        $intermediate = $this->traitFoo();
        return "b$intermediate" . $this->traitBar();
    }
}


class BTest extends \PHPUnit_Framework_TestCase
{

    public function testMe()
    {
        $mock = m::mock(B::class)->makePartial();
        $mock->shouldReceive('traitFoo')->andReturn('c');
        $this->assertEquals('bcd', $mock->foo());
    }
}
Alex Blex
  • 34,704
  • 7
  • 48
  • 75
  • I tried this but other calls to trait methods stop working. Seems like the mocked class isn't aware of the use of the trait. – Gonçalo Marrafa Apr 07 '17 at 08:32
  • You need to update the question then. "stop working" is quite vague. Please see my update for an example where non-mocked trait functions still work. – Alex Blex Apr 07 '17 at 08:40
  • I found out the source of the problem. My method is protected and mockery says "cannot be mocked as it a protected method and mocking protected methods is not allowed for this mock". Am i stuck? – Gonçalo Marrafa Apr 07 '17 at 09:12
  • 1
    I am bit lost in the updates. Could you edit your question with an example that reproduces the problem? If it is just protected `traitFoo`, you need to add `->shouldAllowMockingProtectedMethods()` when you create the mock. – Alex Blex Apr 07 '17 at 09:44
  • With `->shouldAllowMockingProtectedMethods()` it works! Thanks! I can't find reference to this method anywhere in the docs... Is it not documented? – Gonçalo Marrafa Apr 07 '17 at 10:20
  • If it is not documented, it might be to encourage better architecture to avoid mocking protected methods. As I was saying mocking CUT is so far from best practices, that documenting such use of the framework would contradict to the core principals behind it. Remember, that even thou it looks like it is working, you are testing a mockery extension of your real class. It may result with both false-positive and false-negative results, and basically negates value of the test. – Alex Blex Apr 07 '17 at 10:35