Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

bjenkins24's avatar

$this->partialMock doesn't call the constructor?

I guess this is how partialMock works? But to me it seems to be useless now. There's a method I want to mock in a class, but the constructor has to be called or everything will fail. But for some reason it's not getting called? I don't see that in the docs and Google is not helping, which makes me think either I'm doing something very wrong or misunderstanding something very simple.

How is mocking one method useful if the constructor is not called? Here is an example:

Implementation:

<?php

class NoConstructor
{
    private string $foo;

    public function __construct()
    {
        $this->foo = 'bar';
    }

    public function getFoo(): string
    {
        $this->doNothing();
        return $this->foo;
    }

    public function doNothing(): string
    {
        return '';
    }
}

Test:

<?php
use Tests\TestCase;

class NoConstructorTest extends TestCase
{
    public function testNoConstructor()
    {
        $this->partialMock(NoConstructor::class, static function ($mock) {
            $mock->shouldReceive('doNothing')->andReturn('');
        });
        $helloWorld = app(NoConstructor::class)->getFoo();
        self::assertEquals('bar', $helloWorld);
    }
}

Here I get this error when running the test: Typed property App\Modules\NoConstructor::$foo must not be accessed before initialization And yup, the constructor isn't getting called so $foo is being accessed before initialization. What am I missing? How would I mock a single method effectively and still call the constructor?

0 likes
7 replies
Talinon's avatar

@bjenkins24 I think the problem lies with $this->partialMock() which is just a helper function that wraps a call to Mockery, which doesn't account for the constructor.

You could do something like this instead:

 $mock = Mockery::mock(NoConstructor::class, [])->makePartial();

 $this->instance(NoConstructor::class, $mock);

$helloWorld = app(NoConstructor::class)->getFoo(); // should get your desired result


The second parameter to mock() accepts an array of arguments to be passed to the constructor. Providing an empty array should still cause your constructor to be called.

1 like
Yamen's avatar

@talinon I tried your snippet and still getting $foo must not be accessed before initialization

1 like
Mohammad-Alavi's avatar

TL;DR

While I can't directly address the specific problem mentioned by the original poster (OP), I'd like to offer some thoughts on the matter that could potentially provide valuable insights.

Explanation

When using Mockery's runtime partial test doubles (created with makePartial()), the original constructor of the mocked class isn't invoked. But! objects should be instantiated somehow! If I remember correctly, Mockery achieves this by invoking the parent constructor.

It's worth noting that Laravel's $this->partialMock() method essentially utilizes Mockery's makePartial().

In the situation described by the OP, the absence of an original constructor call leads to an uninitialized $foo property. Consequently, when the getFoo() method is invoked, it attempts to access $this->foo, triggering the "Typed property App\Modules\NoConstructor::$foo must not be accessed before initialization" error.

You can gain further insights from this excellent article.

Possible Solutions

Setting Public Properties

One approach is to set the public properties on a mocked object.

$mockedObject->foo = 'bar';

Unfortunately, this option isn't feasible in the OP's case since foo is private.

Passing Arguments to the Constructor

Another strategy involves passing dependencies to the constructor.

$mockedObject = Mockery::mock(MyClass::class, [app(Dependency::class)])->makePartial();

However, a couple of key points to note are:

  1. The second argument in the mock method should be an indexed array, not an associative one. So, use [10] instead of ['foo' => 10].
  2. Laravel's $this->mock, $this->partialMock, and $this->spy helpers do not support passing constructor arguments. For this, you need to utilize Mockery::xxx.
2 likes
ahmad_kash's avatar

I came across this problem recently and i have found the solution in mockery DOC not calling the Original constructor you can use this code if you like

public function PartialMockWithRunningConstructor(string $abstract, array|string $trackedMethods)
    {
        if (is_string($trackedMethods))
            $trackedMethods = Arr::wrap($trackedMethods);

        $mock = Mockery::mock($abstract . '[' .  implode(',', $trackedMethods) . ']');
        $this->instance($abstract, $mock);
        return $mock;
    }
3 likes
Ch1Ch4's avatar

I had similar problem, I want partial mock but constructor not called, this is my solution.

$dependency = $this->mock(Dependency::class);
$service = $this->partialMock(Service::class, function (MockInterface $mock) ) {
            $mock->shouldReceive('mockedMethod')
                ->andReturn('results');
        });
$service->__construct($dependency):

$testResult = $service->notMockedMethod();
5 likes
fico7489's avatar

Ch1Ch4 thank you for explanation, It is really frustrating

Please or to participate in this conversation.