编写单元测试时,一个关键挑战是确保您的测试专注于被测代码,而不受外部系统或依赖项的干扰。这就是模拟对象在PHPUnit中发挥作用的地方。它们允许您以受控方式模拟真实对象的行为,使您的测试更可靠且更易于维护。在本文中,我们将探讨什么是模拟对象、它们为何有用以及如何在 PHPUnit 中有效地使用它们。
模拟对象是在单元测试中使用的真实对象的模拟版本。它们允许您:
模拟在以下场景中特别有用:
使用模拟对象时,您会遇到两个术语:stubbing 和 mocking:
PHPUnit 通过 createMock() 方法可以轻松创建和使用模拟对象。下面是一些示例,演示了如何有效地使用模拟对象。
在此示例中,我们为类依赖项创建一个模拟对象并指定其行为。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { public function testMockExample() { // Create a mock for the SomeClass dependency $mock = $this->createMock(SomeClass::class); // Specify that when the someMethod method is called, it returns 'mocked value' $mock->method('someMethod') ->willReturn('mocked value'); // Pass the mock object to the class under test $unitUnderTest = new ClassUnderTest($mock); // Perform the action and assert that the result matches the expected value $result = $unitUnderTest->performAction(); $this->assertEquals('expected result', $result); } }
说明:
有时,您需要验证是否使用正确的参数调用了方法。具体方法如下:
public function testMethodCallVerification() { // Create a mock object $mock = $this->createMock(SomeClass::class); // Expect the someMethod to be called once with 'expected argument' $mock->expects($this->once()) ->method('someMethod') ->with($this->equalTo('expected argument')) ->willReturn('mocked value'); // Pass the mock to the class under test $unitUnderTest = new ClassUnderTest($mock); // Perform an action that calls the mock's method $unitUnderTest->performAction(); }
要点:
为了演示模拟对象的实际应用,我们以依赖于外部 PaymentGateway 接口的 PaymentProcessor 类为例。我们想要测试 PaymentProcessor 的 processPayment 方法,而不依赖于 PaymentGateway 的实际实现。
这是 PaymentProcessor 类:
class PaymentProcessor { private $gateway; public function __construct(PaymentGateway $gateway) { $this->gateway = $gateway; } public function processPayment(float $amount): bool { return $this->gateway->charge($amount); } }
现在,我们可以为 PaymentGateway 创建一个模拟来测试 processPayment 方法,而无需与实际的支付网关交互。
use PHPUnit\Framework\TestCase; class PaymentProcessorTest extends TestCase { public function testProcessPayment() { // Create a mock object for the PaymentGateway interface $gatewayMock = $this->createMock(PaymentGateway::class); // Define the expected behavior of the mock $gatewayMock->method('charge') ->with(100.0) ->willReturn(true); // Inject the mock into the PaymentProcessor $paymentProcessor = new PaymentProcessor($gatewayMock); // Assert that processPayment returns true $this->assertTrue($paymentProcessor->processPayment(100.0)); } }
测试分解:
You can also verify that the charge method is called exactly once when processing a payment:
public function testProcessPaymentCallsCharge() { $gatewayMock = $this->createMock(PaymentGateway::class); // Expect the charge method to be called once with the argument 100.0 $gatewayMock->expects($this->once()) ->method('charge') ->with(100.0) ->willReturn(true); $paymentProcessor = new PaymentProcessor($gatewayMock); $paymentProcessor->processPayment(100.0); }
In this example, expects($this->once()) ensures that the charge method is called exactly once. If the method is not called, or called more than once, the test will fail.
Let’s assume you have a UserService class that depends on a UserRepository to fetch user data. To test UserService in isolation, you can mock the UserRepository.
class UserService { private $repository; public function __construct(UserRepository $repository) { $this->repository = $repository; } public function getUserName($id) { $user = $this->repository->find($id); return $user->name; } }
To test this class, we can mock the repository:
use PHPUnit\Framework\TestCase; class UserServiceTest extends TestCase { public function testGetUserName() { // Create a mock for the UserRepository $mockRepo = $this->createMock(UserRepository::class); // Define that the find method should return a user object with a predefined name $mockRepo->method('find') ->willReturn((object) ['name' => 'John Doe']); // Instantiate the UserService with the mock repository $service = new UserService($mockRepo); // Assert that the getUserName method returns 'John Doe' $this->assertEquals('John Doe', $service->getUserName(1)); } }
Mock objects are invaluable tools for writing unit tests in PHPUnit. They allow you to isolate your code from external dependencies, ensuring that your tests are faster, more reliable, and easier to maintain. Mock objects also help verify interactions between the code under test and its dependencies, ensuring that your code behaves correctly in various scenarios
以上是了解 PHPUnit 测试中的模拟对象的详细内容。更多信息请关注PHP中文网其他相关文章!