Mockery & PHPUnit: method does not exist on this mock object
Asked Answered
B

1

9

Can you tell me where's the problem? I have a file GeneratorTest.php with the following tests:

<?php

namespace stats\Test;

use stats\jway\File;
use stats\jway\Generator;

class GeneratorTest extends \PHPUnit_Framework_TestCase
{

    public function tearDown() {
        \Mockery::close();
    }

    public function testGeneratorFire()
    {
        $fileMock = \Mockery::mock('\stats\jway\File');
        $fileMock->shouldReceive('put')->with('foo.txt', 'foo bar')->once();
        $generator = new Generator($fileMock);
        $generator->fire();
    }

    public function testGeneratorDoesNotOverwriteFile()
    {
        $fileMock = \Mockery::mock('\stats\jway\File');
        $fileMock->shouldReceive('exists')
            ->once()
            ->andReturn(true);

        $fileMock->shouldReceive('put')->never();

        $generator = new Generator($fileMock);
        $generator->fire();
    }
}

and here are File and Generator classes:

File.php:

class File
{
    public function put($path, $content)
    {
        return file_put_contents($path, $content);
    }

    public function exists($file_path)
    {
        if (file_exists($file_path)) {
            return true;
        }
        return false;
    }
}

Generator.php:

class Generator
{
    protected $file;

    public function __construct(File $file)
    {
        $this->file = $file;
    }

    protected function getContent()
    {
        // simplified for demo
        return 'foo bar';
    }

    public function fire()
    {
        $content = $this->getContent();
        $file_path = 'foo.txt';

        if (! $this->file->exists($file_path)) {
            $this->file->put($file_path, $content);
        }
    }

}

So, when I run these tests, I get the following message: BadMethodCallException: Method ... ::exists() does not exist on this mock object.

enter image description here

Bonus answered 20/5, 2016 at 14:12 Comment(2)
try adding withAnyArgs() to the mocking of the file as example: $fileMock->shouldReceive('exists') ->once()->withAnyArgs() ->andReturn(true);Coltson
I tried, the same problem still exists.Bonus
G
13

The error message seems clear to me. You only did setup a expectation for the put method, but not exists. The exists method is called by the class under test in all code paths.

public function testGeneratorFire()
{
    $fileMock = \Mockery::mock('\stats\jway\File');
    $fileMock->shouldReceive('put')->with('foo.txt', 'foo bar')->once();

    //Add the line below
    $fileMock->shouldReceive('exists')->once()->andReturn(false);

    $generator = new Generator($fileMock);
    $generator->fire();
}
Gelid answered 21/5, 2016 at 9:38 Comment(4)
One question: when we create a Mock object like this: $fileMock = \Mockery::mock('\stats\jway\File'); then all methods in that Mock object will return NULL by default, right?. If yes, then why we need to setup exists method like you did, it will retun NULL and therefore put method will be executed anyway?Bonus
You have to add shouldIgnoreMissing() to let the mock object return null when you didn't setup an expectation. In this case it would be better to be strict and return false, because a boolean value is what the interface of exists defines. If you rewrite if (! $this->file->exists($file_path)) to if ($this->file->exists($file_path) === false)) your unit test will break.Gelid
"You only did setup a expectation for the put method, but not exists. The exists method is called by the class under test in all code paths." Could you explain this more? Why do we need to set up test expectations for methods that are called incidentally?Blinker
shouldIgnoreMissing() definitely helped me avoid some bottlenecks.Pinkerton

© 2022 - 2024 — McMap. All rights reserved.