Mocked protected method not returning value set in mock.

huangapple go评论57阅读模式
英文:

Mocked protected method not returning value set in mock

问题

This constructor loads a JSON file using the load method. The error you're encountering, "Failed to open stream: No such file or directory," suggests that the file specified by the manifestPath method does not exist. You need to ensure that the file at the path returned by manifestPath exists before attempting to load it with file_get_contents.

In your test code, you are using mocking to avoid the actual file system operations. To resolve this issue, you can mock the File::exists method to return true in your test case so that it doesn't throw an exception. Here's how you can modify your test code:

/** @testdox Returns an object when the path exists */
public function test_manifest_returns_object() : void
{
    // Mock File::exists to return true
    File::shouldReceive('exists')->once()->andReturnTrue();

    // Mock the load method to return a JSON-decoded object
    $this->mock(ModuleManifest::class, function (MockInterface $mock) {
        $mock->shouldAllowMockingProtectedMethods();
        $mock->shouldReceive('load')->once()->andReturn(
            json_decode('{"name": "testModule"}')
        );
    });

    $manifest = new ModuleManifest('vendor', 'package');

    $this->assertTrue($manifest->has('name'));
}

By mocking File::exists to return true, you ensure that the code within the constructor proceeds without throwing an exception due to a missing file, and your test should pass as expected.

英文:

I have a class called ModuleManifest with this constructor:

/**
 * Create a new instance of the ModuleManifest class.
 *
 * @param string $vendor The vendor of the module.
 * @param string $package The name of the package.
 * @return void
 */
public function __construct(string $vendor, string $package)
{
    $manifest = $this->manifestPath($vendor, $package);

    if (!File::exists($manifest))
    {
        throw new \InvalidArgumentException(sprintf(
            'Package %s from vendor %s does not contain a manifest',
            $package, $vendor
        ));
    }

    $this->manifest = $this->load($manifest);
}

This constructor loads a json file with the load method.

/**
 * Load a module manifest into an object.
 *
 * @param string $path The path to the module.
 * @return object
 */
protected function load(string $path) : object
{
    return json_decode(file_get_contents($path));
}

/**
 * Determine the path to the module manifest.
 *
 * @param string $vendor The vendor of the module.
 * @param string $package The name of the package.
 * @return string
 */
protected function manifestPath(string $vendor, string $package) : string
{
    return implode('/', [base_path('vendor'), $vendor, $package, 'resources/manifest.json']);
}

I want determine whether the method is returning an object:

/** @testdox Returns an object when the path exists */
public function test_manifest_returns_object() : void
{
    File::shouldReceive('exists')->once()->andReturnTrue();

    $this->mock(ModuleManifest::class, function (MockInterface $mock) {
        $mock->shouldAllowMockingProtectedMethods();
        $mock->shouldReceive('load')->once()->andReturn(
            json_decode('{"name": "testModule"}')
        );
    });

    $manifest = new ModuleManifest('vendor', 'package');

    $this->assertTrue($manifest->has('name'));
}

I get the error:

> ErrorException: file_get_contents(...): Failed to open stream: No such file or directory

I would expect it to return the result of json_decode('{"name": "testModule"}'), but it tries to call the function normally. How to resolve this? I have tried to create a partial mock, but the issue remains.

答案1

得分: 2

以下是翻译好的部分:

这是我将如何操作的方式。在构造函数中添加一个可选的 `$basePath` 参数,其默认值为 `base_path()`

class ModuleManifest
{
	private object $manifest;

	public function __construct(string $vendor, string $package, string $basePath='')
	{
		if ($basePath=='')
			$basePath = base_path();
		$path = $this->manifestPath($vendor, $package, $basePath);

		if (!File::exists($path))
		{
			throw new \InvalidArgumentException(sprintf(
				'来自供应商 %s 的包 %s 不包含清单',
				$package, $vendor
			));
		}

		$this->manifest = $this->load($path);
	}

	protected function manifestPath(string $vendor, string $package, string $basePath) : string
	{
		return implode('/', [$basePath, 'vendor', $vendor, $package, 'resources/manifest.json']);
	}

	protected function load(string $path) : object
	{
		return json_decode(file_get_contents($path));
	}

	public function has(string $key) : bool
	{
		return isset($this->manifest->$key);
	}
}

对于测试,请选择一个测试目录并在其中创建 vendor/foo/bar/resources/manifest.json。然后测试代码如下:

public function test_manifest_returns_object() : void
{
    $manifest = new ModuleManifest('foo', 'bar', '/path/to/test/dir');
    $this->assertTrue($manifest->has('name'));
}

就是这样。您不需要任何模拟。还请注意,不应该测试 ModuleManifest 使用 File 类,因为它只是一个实现细节。它完全可以以另一种方式执行(例如直接调用 file_exists())。

英文:

Here is how I would do it. Add an optional $basePath parameter to the constructor whose default
value is base_path():

class ModuleManifest
{
	private object $manifest;

	public function __construct(string $vendor, string $package, string $basePath='')
	{
		if ($basePath=='')
			$basePath = base_path();
		$path = $this->manifestPath($vendor, $package, $basePath);

		if (!File::exists($path))
		{
			throw new \InvalidArgumentException(sprintf(
				'Package %s from vendor %s does not contain a manifest',
				$package, $vendor
			));
		}

		$this->manifest = $this->load($path);
	}

	protected function manifestPath(string $vendor, string $package, string $basePath) : string
	{
		return implode('/', [$basePath, 'vendor', $vendor, $package, 'resources/manifest.json']);
	}

	protected function load(string $path) : object
	{
		return json_decode(file_get_contents($path));
	}

	public function has(string $key) : bool
	{
		return isset($this->manifest->$key);
	}
}

For the test, choose a test directory and create vendor/foo/bar/resources/manifest.json in it. Then the testing
code is simply:

public function test_manifest_returns_object() : void
{
    $manifest = new ModuleManifest('foo', 'bar', '/path/to/test/dir');
    $this->assertTrue($manifest->has('name'));
}

That's it. You don't need any mock. Also note that you should not test that ModuleManifest uses the File class because
it's just an implementation detail. It could very well do it another way (such as calling file_exists() directly).

huangapple
  • 本文由 发表于 2023年6月26日 17:07:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76555193.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定