问题
I'm trying to test a method using PHPUnit, where it calls another function (standalone function, without class), which resides in different file which does a some pretty good calculation and returns a object.
This is my actual main code:
class CreateRecords
{
public function createEntities($details)
{
if (trim($details['username']) == "") {
$this->result = "Username is empty.";
} else {
$this->result = create_record($Details['username']);
}
return $this->result;
}
}
This create_record
function, (standalone function, without class), which is core function, resides in separate file and it does pretty good calculations (calls lots of other methods/functions) and returns object, whether it is successful or not.
I can mock the createEntities
method, but I want to mock the create_record
function, which does all the computations and returns the result.
I have seen few posts which has a somewhat similar scenario,
phpunit testing method that calls other class methods which need mock
PHPUnit mock method used in another class
But I am unable to understand, how to mock standalone function which is declared in some different file.
回答1:
You can create new method that will be returning result from outside function. Then you can mock this new method
class CreateRecords
{
public function createEntities($details)
{
if (trim($details['username']) == "") {
$this->result = "Username is empty.";
} else {
$this->result = $this->createRecord($Details['username']);
}
return $this->result;
}
public function createRecord($username){
return create_record($username);
}
}
回答2:
Namespaces in PHP5.3+ offer an excellent solution to this, which allows you to override built-in functions in your current namespace.
Longer term, refactoring the global create_record()
into a class which can be injected into your CreateRecords
class, and then mocked, would be a good way to go. In this instance, the global function would just be a simple wrapper to call the class until the entire application was updated.
To re-create the create_record($username)
is not hard though, and useful for tests. The same technique can be used to override the global time()
function as well.
Within the test-file, add a new (but local) namespace:
<?php
namespace Test\DatabaseAccess;
use DatabaseAccess\CreateRecord;
use PHPUnit\Framework\TestCase;
namespace DatabaseAccess {
function create_record($username)
{
// pretend to do something
}
}
class CreateRecordTest extends TestCase
{
// test to check CreateRecord class
// which should call the `create_record`, above
}
This is the same technique that the SymfonyBridge system uses to create ClockMock - which dynamically adds time()
, sleep()
, etc to the namespace that you are unit-testing (in this example, the namespace DatabaseAccess\CreateRecord
, not the Test\ prefixed namespace). ClockMock (and DnsMock) does it with an eval()
call, but since you know the namespace explicitly, you can write it into the test file itself for clarity.
回答3:
After reading the above (pretty good) answers and your comments saying that you cannot touch the tested class - CreateRecords
,
I can suggest another solution that is not ideal but should get the job done:
Create a new class that inherits\ extends from CreateRecords
- CreateRecordsExtended
.
The extended class should override only the tested function in question createEntities($details)
. so create a new one and copy the code from the original function.
Also, create a new function create_record()
.
Now, inside the new createEntitied
, call your version of create_record - $this->create_record()
, instead of calling the global function.
Now you can mock it! and even because this class is used only for testing, you can even just retun whatever you want from it, and do not even have to mock it now.
This class can reside either in the regular code folder, or as a neighbor of your testing class- as it is used only for testing.
Pros:
- existing code is not modified at all.
- you still use same exact functionality, besides the mocked function, which is what you wanted.
Cons:
- you will be testing a different class then the one you wanted to, but it still close enough.
- code inside function
createEntities
needs to be copied
Perhaps its not most ideal, but should get the job done. hope i helped.
来源:https://stackoverflow.com/questions/47436445/phpunit-how-to-test-a-method-which-calls-another-function-declared-in-different