英文:
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).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论