ChristopherSFSD's avatar

Need help with first mocking / stubbing test

Hello I'm trying to get my first mocking or stubbing test working with Prophecy. I've never used mocks and stubs or Mockery. I've done a fair number of unit tests where dependencies didn't really come into play, etc.

If I can get this one test working, I think it'll help me go a long with with further testing my code. Any help would be greatly appreciated!

First the error ...

Method `Double\App\Services\Maintenance\Flags\MaintenanceFlagsProvider\P1::findMostRecentByLabel()` is not defined.
/Library/WebServer/App/tests/unit/MaintenanceStatusTests.php:21

Here are the relevant portions of the test class ...

use App\Services\Maintenance\Logs\MaintenanceLogProvider;
use App\Services\Maintenance\Flags\MaintenanceFlagsProvider;
use App\Services\Maintenance\NextDueSchedules\NextDueScheduleProvider;
use App\Services\Components\Requirements\Status\RequirementStatusProvider;
use App\Services\Components\Requirements\Properties\ComponentRequirementPropertiesProvider;

class MaintenanceStatusTests extends TestCase
{
    /** @test */
    public function is_initial_returns_true()
    {
        $status = $this->buildStatus();
        
        $status->maintenance_flags_provider->shouldReceive('findMostRecentByLabel')->andReturn(new MaintenanceFlag(['value' => 1]));

        $this->assertTrue($status->isInitial());

    }

    private function buildStatus()
    {
        $maintenance_flags_provider = $this->prophesize(MaintenanceFlagsProvider::class);
        $maintenance_log_provider = $this->prophesize(MaintenanceLogProvider::class);
        $next_due_schedule_provider = $this->prophesize(NextDueScheduleProvider::class);
        $component_requirement_properties_provider = $this->prophesize(ComponentRequirementPropertiesProvider::class);

        return new RequirementStatusProvider($maintenance_flags_provider->reveal(), $maintenance_log_provider->reveal(), $next_due_schedule_provider->reveal(), $component_requirement_properties_provider->reveal());
    }
}

Here are the relevant portions of the class/method I'm attempting to test ...

namespace App\Services\Components\Requirements\Status;

use Carbon, StdClass;
use App\Services\Maintenance\Logs\MaintenanceLogProvider;
use App\Services\Maintenance\Flags\MaintenanceFlagsProvider;
use App\Services\Maintenance\NextDueSchedules\NextDueScheduleProvider;
use App\Services\Components\Requirements\Properties\ComponentRequirementPropertiesProvider;

class RequirementStatusProvider
{
    public $maintenance_flags_provider;
    public $maintenance_log_provider;
    public $next_due_schedule_provider;
    public $component_requirement_properties_provider;

    public $data;

    /**
     * @param MaintenanceFlagsProvider $maintenance_flags_provider
     * @param MaintenanceLogProvider $maintenance_log_provider
     * @param NextDueScheduleProvider $next_due_schedule_provider
     * @param ComponentRequirementPropertiesProvider $component_requirement_properties_provider
     */
    public function __construct(MaintenanceFlagsProvider $maintenance_flags_provider, MaintenanceLogProvider $maintenance_log_provider, NextDueScheduleProvider $next_due_schedule_provider, ComponentRequirementPropertiesProvider $component_requirement_properties_provider)
    {
        $this->data = new StdClass();

        $this->maintenance_flags_provider = $maintenance_flags_provider;
        $this->maintenance_log_provider = $maintenance_log_provider;
        $this->next_due_schedule_provider = $next_due_schedule_provider;
        $this->component_requirement_properties_provider = $component_requirement_properties_provider;
    }

// THIS IS WHAT I'M TRYING TO TEST ....
    public function isInitial()
    {
        $flag = $this->maintenance_flags_provider->findMostRecentByLabel('initial', $this->component->id, $this->requirement->id, $this->data->datetime);

        return (($flag && 1 == $flag->getAttribute('value')) || ! $this->data->pcw_logs[0]->event_at) ? 1 : 0;
    }
}

I have also tried revising the test in the following way ...

/** @test */
public function is_initial_returns_true()
{
$maintenance_log_provider = $this->prophesize(MaintenanceLogProvider::class);
$maintenance_flags_provider = $this->prophesize(MaintenanceFlagsProvider::class);
$next_due_schedule_provider = $this->prophesize(NextDueScheduleProvider::class);
$component_requirement_properties_provider = $this->prophesize(ComponentRequirementPropertiesProvider::class);

$maintenance_flags_provider->findMostRecentByLabel('initial', 1, 1, Carbon::now())->willReturn(new MaintenanceFlag(['value' => 1]));

$status = new RequirementStatusProvider($maintenance_flags_provider->reveal(), $maintenance_log_provider->reveal(), $next_due_schedule_provider->reveal(), $component_requirement_properties_provider->reveal());

$this->assertTrue($status->isInitial());
}

Even if I omit the ->willReturn portion I still get the exact same error.

The goal is to have $flag set to whatever I define it to be in the so as to not use the actual MaintenanceFlagsProvider

0 likes
3 replies
ChristopherSFSD's avatar

I'm pretty sure that what I'm trying to do is tell the Mock ($maintenance_flags_provider) that it should call findMostRecentByLabel and then return a new instance of MaintenanceFlag.

I'm struggling to figure out how to do that. I saw that Prophecy has a MethodProphecy class and have tried generating one of those but still no luck.

ChristopherSFSD's avatar

Looks like the issue for me is that the MaintenanceFlagsProvider uses __call to forward undefined methods to my MaintenanceFlags repository class. This seems to be what's tripping up Prophecy.

ChristopherSFSD's avatar
Level 4

Okay so the solution to the __call problem is to add an @method tag to the provider class for methods that would be resolved by the __call magic method ...

namespace App\Services\Maintenance\Flags;

use App\Storage\Maintenance\FlagsRepository as FlagsRepo;


/**
 * @method findMostRecentByLabel(array $args = [])
 */
class MaintenanceFlagsProvider
{
    private $maintenance_flags_repo;

    /**
     * @param FlagsRepo $maintenance_flags_repo
     */
    public function __construct(FlagsRepo $maintenance_flags_repo)
    {
        $this->maintenance_flags_repo = $maintenance_flags_repo;
    }

    /**
     * Pass undefined method calls to the repo
     *
     * @param $method
     * @param $args
     * @return mixed
     */
    public function __call($method, $args)
    {
        return call_user_func_array(array($this->maintenance_flags_repo, $method), $args);
    }
}

And then the test would look like this ...

/** @test */
public function is_initial_returns_true()
{
    $date = Carbon::now();

    $maintenance_log_provider = $this->prophesize(MaintenanceLogProvider::class);
    $maintenance_flags_provider = $this->prophesize(MaintenanceFlagsProvider::class);
    $next_due_schedule_provider = $this->prophesize(NextDueScheduleProvider::class);
    $component_requirement_properties_provider = $this->prophesize(ComponentRequirementPropertiesProvider::class);

    $maintenance_flags_provider->findMostRecentByLabel('initial', 1, 1, $date)->shouldBeCalled()->willReturn(new MaintenanceFlag(['value' => 1]));

    $status = new RequirementStatusProvider($maintenance_flags_provider->reveal(), $maintenance_log_provider->reveal(), $next_due_schedule_provider->reveal(), $component_requirement_properties_provider->reveal());

    $status->component = (new Component())->forceFill(['id' => 1]);
    $status->requirement = (new MaintenanceRequirement())->forceFill(['id' => 1]);
    $status->data->datetime = $date;

    $this->assertTrue($status->isInitial());
}

Please or to participate in this conversation.