You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PHPUnit中Mock Worldpay自表单订阅支付功能?

Testing Worldpay Own-Form Subscription Payments with PHPUnit

Great question! Since the Worldpay "own form" relies on JavaScript to generate the payment token and pass it to your backend as $_POST['token'], you don't need to mock the frontend JS flow at all. Your PHPUnit tests should focus solely on how your backend handles the token once it's received, plus mocking the Worldpay API calls to avoid real transactions. Here's a step-by-step guide with concrete examples:


1. Key Principles to Follow

First, let's clarify the separation of concerns:

  • The frontend JS's job is to generate a valid token (Worldpay handles the security here)
  • Your backend's job is to receive that token, validate its presence, and use it to call Worldpay's API to process the subscription
  • Your tests should validate your backend logic, not Worldpay's JS or API (we'll mock those parts)

2. Example Test Setup

Assume you have a controller like SubscriptionController with a processPayment() method that handles the form submission. First, make sure your code uses dependency injection for the Worldpay service (this is critical for easy mocking).

Sample Controller Code

namespace App\Controllers;

use App\Services\WorldpayService;
use App\Exceptions\WorldpayPaymentException;

class SubscriptionController
{
    private $worldpayService;

    public function __construct(WorldpayService $worldpayService)
    {
        $this->worldpayService = $worldpayService;
    }

    public function processPayment(): array
    {
        // Validate token exists
        if (!isset($_POST['token']) || trim($_POST['token']) === '') {
            return [
                'status' => 'error',
                'message' => 'Payment token is missing'
            ];
        }

        try {
            // Use token to create subscription via Worldpay
            $subscription = $this->worldpayService->createSubscription(
                $_POST['token'],
                19.99, // Monthly price
                'USD',
                'Premium Plan'
            );

            return [
                'status' => 'success',
                'subscription_id' => $subscription['id'],
                'message' => 'Subscription created successfully'
            ];
        } catch (WorldpayPaymentException $e) {
            return [
                'status' => 'error',
                'message' => $e->getMessage()
            ];
        }
    }
}

PHPUnit Test Class

Create a test class that mocks the WorldpayService and simulates different $_POST scenarios:

use PHPUnit\Framework\TestCase;
use App\Controllers\SubscriptionController;
use App\Services\WorldpayService;
use App\Exceptions\WorldpayPaymentException;

class SubscriptionControllerTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        // Reset $_POST before each test to avoid cross-test contamination
        $_POST = [];
    }

    /** @test */
    public function process_payment_returns_success_with_valid_token()
    {
        // 1. Simulate a valid token being posted to the backend
        $_POST['token'] = 'wp-valid-test-token-12345';

        // 2. Mock the WorldpayService to return a successful subscription
        $mockWorldpay = $this->createMock(WorldpayService::class);
        $mockWorldpay->expects($this->once())
            ->method('createSubscription')
            ->with('wp-valid-test-token-12345', 19.99, 'USD', 'Premium Plan')
            ->willReturn(['id' => 'sub-67890']);

        // 3. Initialize controller with mocked service
        $controller = new SubscriptionController($mockWorldpay);
        $response = $controller->processPayment();

        // 4. Assert the response is correct
        $this->assertEquals('success', $response['status']);
        $this->assertEquals('sub-67890', $response['subscription_id']);
    }

    /** @test */
    public function process_payment_returns_error_when_token_is_missing()
    {
        // 1. Simulate empty POST data (no token)
        $_POST = [];

        // 2. Mock WorldpayService (it should NEVER be called here)
        $mockWorldpay = $this->createMock(WorldpayService::class);
        $mockWorldpay->expects($this->never())->method('createSubscription');

        // 3. Run the controller method
        $controller = new SubscriptionController($mockWorldpay);
        $response = $controller->processPayment();

        // 4. Assert error response
        $this->assertEquals('error', $response['status']);
        $this->assertEquals('Payment token is missing', $response['message']);
    }

    /** @test */
    public function process_payment_returns_error_when_worldpay_rejects_token()
    {
        // 1. Simulate an invalid token
        $_POST['token'] = 'wp-invalid-test-token-54321';

        // 2. Mock WorldpayService to throw an exception
        $mockWorldpay = $this->createMock(WorldpayService::class);
        $mockWorldpay->expects($this->once())
            ->method('createSubscription')
            ->with('wp-invalid-test-token-54321', 19.99, 'USD', 'Premium Plan')
            ->willThrowException(new WorldpayPaymentException('Invalid or expired payment token'));

        // 3. Run the controller method
        $controller = new SubscriptionController($mockWorldpay);
        $response = $controller->processPayment();

        // 4. Assert error handling works
        $this->assertEquals('error', $response['status']);
        $this->assertEquals('Invalid or expired payment token', $response['message']);
    }
}

3. Additional Tips

  • Refactor if needed: If your current code uses a global Worldpay client instead of dependency injection, refactor it to accept the service via constructor injection. This makes mocking trivial.
  • Test edge cases: Add tests for scenarios like:
    • Token with whitespace (e.g., ' wp-token-123 ')
    • Worldpay API returning other error types (e.g., insufficient funds, network timeout)
  • Avoid real API calls: Never use real Worldpay credentials in tests. Always mock the service layer to return predefined responses.

内容的提问来源于stack exchange,提问作者Andreas Christodoulou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:49:39