2

I've encountered this issue a few times now in my task to add unittests to the project, the issue is that I need to return a function as return value (and cant change the class itself). Small example:

// The code has a:
$thing->doFoo()->getBar();

// So I start the mock:
$thingMock = $this->createMock(Thing::class);
$thingMock->method('doFoo')->willReturn(/* WHAT DO I WRITE HERE? */);

Then part two of the issue, I want to make a helperfunction where I can define the result of getBar:

private function createThingMock($example){
    $thingMock = $this->createMock(Thing::class);
    $thingMock->method('doFoo')->willReturn(/* WHAT HERE? I must return $example */);
}

I currently have the following, but it's very bulky after a code format:

private function createThingMock($example){
    $thingMock = $this->createMock(Thing::class);
    $thingMock->method('doFoo')->willReturn(
        new class($options)
        {
            private $options;
            public function __construct($options) {
                $this->options = $options;
            }

           public function getBar(): string
            {
                return $example ?? 'currently $example is always null';
            }
        }
    );
}

That is the result after a code format and as you can see it's very bulky for something that feels it could be done a lot more simple. Suggestions?

Martijn
  • 15,791
  • 4
  • 36
  • 68
  • 1
    `$thing->doFoo()` must return an object of a specific type (at least from inheritance point of view), so should that return a mock of the returned class which can then implement the `getBar()` method? – Nigel Ren Dec 17 '19 at 13:39
  • The current issue which is blocking me is a class I can't simply mock (I believe it's final or abstract). I just need it to return a string, I dont need the original function (so I'm somewhat lucky). – Martijn Dec 17 '19 at 13:41

1 Answers1

0

I'm not sure to understand your problem, so excuse me if I answer off the mark.

I assume that Thing::doFoo() is returning an instance of class named SubThing. I assume that class SubThing has a method named doBar() returning a string

// The code has a:
$thing->doFoo()->getBar();

// So I start the mock:
$subThingMock = $this->createMock(SubThing::class);
$subThingMock->method('doBar')->willReturn('sample string');

$thingMock = $this->createMock(Thing::class);
$thingMock->method('doFoo')->willReturn($subThingMock);

If SubThing is an implementation of interface named ThingInterface, mock your interface:

// So I start by mocking interface:
$subThingMock = $this->createMock(ThingInterface::class);
$subThingMock->method('doBar')->willReturn('sample string');

$thingMock = $this->createMock(Thing::class);
$thingMock->method('doFoo')->willReturn($subThingMock);

If SubThing is an instance of a subclass of abstract class AbstractThing, you can mock it too:

$subThingMock = $this->getMockForAbstractClass(AbstractThing::class);
$subThingMock->method('doBar')->willReturn('sample string');

$thingMock = $this->createMock(Thing::class);
$thingMock->method('doFoo')->willReturn($subThingMock);

In any case, if you only want to return a string, you can create a substitution class:

class Substitution {
   public function doBar(){
     return 'sample string';
   }
}

$subThing = new Substitution();

$thingMock = $this->createMock(Thing::class);
$thingMock->method('doFoo')->willReturn($subThing); //$subThing, not $subThingMock
Alexandre Tranchant
  • 4,426
  • 4
  • 43
  • 70