arrayssymfonypass-by-referenceprophecy

Symfony ObjectProphecy treats array and object differently - can't change array which was initialised in setUp method


Related to this

I am trying out this test code

<?php

use Monolog\Test\TestCase;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophet;

class MyBar {
    public int $bar = 0;
}

class MyFoo {
    public function fooArray(): array {
        return [];
    }

    public function fooBar(): MyBar {
        return new MyBar();
    }

    public function fooInt(): int {
        return 0;
    }
}

final class SimpleTest extends TestCase
{
    private Prophet $prophet;
    private ObjectProphecy $mock;
    private array $myArray;
    private MyBar $myBar;
    private int $myInt;

    public function getProphet(): Prophet {
        return $this->prophet;
    }

    protected function setUp(): void {
        $this->prophet = new Prophet();
        $this->myArray = [0];
        $this->myBar = new MyBar();
        $this->myInt = 0;
        $this->mock = $this->getProphet()->prophesize(MyFoo::class);
        $this->mock->fooArray()->willReturn($this->myArray);
        $this->mock->fooBar()->willReturn($this->myBar);
        $this->mock->fooInt()->willReturn($this->myInt);
    }
    
    public function test(): void {
        $this->myArray = [1];
        $this->assertEquals([0], $this->mock->reveal()->fooArray());

        $this->myBar->bar = 1;
        $this->assertEquals(1, $this->mock->reveal()->fooBar()->bar);

        $this->myInt = 1;
        $this->assertEquals(0, $this->mock->reveal()->fooInt());
    }
}


Notice the difference in test function how array $myArray doesn't get updated, nor does int $myInt, but object $myBar does.

I was thinking about passing the array as a reference, but according to this, it might not be possible with ObjectProphecy. The answer to the question I linked at the top suggests the solution using another approach with MockObject, but I am trying to see whether there is a solution within ObjectProphecy.


Solution

  • You are testing something completely different (comparing tomato with potato). Hopefully I have explained the situation well enough below.

    To answer your first question, I used $this->myInt as an example (however, this also concerns $this->myArray);

    In your setup() function you are using three steps:

    1. Your first step is assigning 0 to $this->myInt:

      $this->myInt = 0;

    2. Your second step is creating an object:

      $this->mock = $this->getProphet()->prophesize(MyFoo::class);

    3. Your third step (#3) is assigning the object with value 0 (from $this->myInt):
      $this->mock->fooInt()->willReturn($this->myInt);

    Then in your actual test function (test()):

    1. As a fourth step (#4), you are changing $this->myInt value to 1:

      $this->myInt = 1;

    2. As a fifth step (#5) you are actually testing the value of the object which according to step #3 should still return 0:

      $this->assertEquals(0, $this->mock->reveal()->fooInt());

    In the last step, you are actually comparing 0 with the value you set in step #3 (which was $this->myInt = 0 accoring to step #1). E.g. your test will pass. Step #4 is not doing anything, since you are not changing the value of your object (which you are testing), you are only changing the value of the private parameter within your class. Your expected behaviour only works when you test the value of $this->myInt, so like this:

    $this->assertEquals(0, $this->myInt);

    So to solve your problem, either rewrite your test OR make sure that you are changing the correct value (either within your class or within the object).

    To explain your object example and why this is different, you are actually changing the value within your object and testing the same value in you test (testing it correctly):

    $this->myBar->bar = 1;
    $this->assertEquals(1, $this->mock->reveal()->fooBar()->bar);
    

    You changed the value within the object. Once again, your test will pass.

    To answer your second question, change step #4 into:

    $this->myInt = 1;
    $this->mock->fooInt()->willReturn($this->myInt);
    // and then to do your test from step #5:
    $this->assertEquals(0, $this->mock->reveal()->fooInt());
    // this should fail.