7

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

PeraMika
  • 3,539
  • 9
  • 38
  • 63

1 Answers1

12

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();
}
Bram Gerritsen
  • 7,178
  • 4
  • 35
  • 45
  • 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? – PeraMika May 21 '16 at 13:21
  • 2
    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. – Bram Gerritsen May 21 '16 at 16:33
  • "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? – alexw Jun 03 '17 at 01:56
  • `shouldIgnoreMissing()` definitely helped me avoid some bottlenecks. – Moses Ndeda Jul 24 '17 at 10:11