Testing
Overview
The M-Pesa SDK provides comprehensive testing capabilities to help you ensure your integration works correctly. This guide explains how to write and run tests for your M-Pesa integration.
Basic Testing Setup
1. Install Testing Dependencies
composer require --dev phpunit/phpunit
2. Create Test Configuration
// phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
colors="true"
verbose="true"
stopOnFailure="false">
<testsuites>
<testsuite name="M-Pesa SDK Tests">
<directory>tests</directory>
</testsuite>
</testsuites>
<php>
<env name="MPESA_ENVIRONMENT" value="sandbox"/>
<env name="MPESA_CONSUMER_KEY" value="test_key"/>
<env name="MPESA_CONSUMER_SECRET" value="test_secret"/>
</php>
</phpunit>
Unit Testing
1. Configuration Tests
// tests/ConfigTest.php
<?php
use PHPUnit\Framework\TestCase;
use MesaSDK\PhpMpesa\Config;
class ConfigTest extends TestCase
{
public function testConfigInitialization()
{
$config = new Config();
$config->setBaseUrl("https://apisandbox.safaricom.et")
->setConsumerKey("test_key")
->setConsumerSecret("test_secret")
->setEnvironment('sandbox');
$this->assertEquals("https://apisandbox.safaricom.et", $config->getBaseUrl());
$this->assertEquals("test_key", $config->getConsumerKey());
$this->assertEquals("test_secret", $config->getConsumerSecret());
$this->assertEquals("sandbox", $config->getEnvironment());
}
public function testConfigValidation()
{
$this->expectException(ValidationException::class);
$config = new Config();
$config->validate(); // Should throw exception for missing required fields
}
}
2. Response Tests
// tests/ResponseTest.php
<?php
use PHPUnit\Framework\TestCase;
use MesaSDK\PhpMpesa\Response\MpesaResponse;
class ResponseTest extends TestCase
{
public function testSuccessfulResponse()
{
$data = [
'ResponseCode' => 0,
'ResponseDesc' => 'Success',
'MerchantRequestID' => '12345-67890-1'
];
$response = MpesaResponse::fromArray($data);
$this->assertTrue($response->isSuccessful());
$this->assertEquals('Success', $response->getResponseDesc());
$this->assertEquals('12345-67890-1', $response->getMerchantRequestID());
}
public function testFailedResponse()
{
$data = [
'ResponseCode' => 1,
'ResponseDesc' => 'Failed',
'ErrorMessage' => 'Invalid credentials'
];
$response = MpesaResponse::fromArray($data);
$this->assertFalse($response->isSuccessful());
$this->assertEquals('Failed', $response->getResponseDesc());
$this->assertEquals('Invalid credentials', $response->getErrorMessage());
}
}
Integration Testing
1. Authentication Tests
// tests/AuthenticationTest.php
<?php
use PHPUnit\Framework\TestCase;
use MesaSDK\PhpMpesa\Config;
use MesaSDK\PhpMpesa\Mpesa;
class AuthenticationTest extends TestCase
{
private $mpesa;
protected function setUp(): void
{
$config = new Config();
$config->setBaseUrl("https://apisandbox.safaricom.et")
->setConsumerKey(getenv('MPESA_CONSUMER_KEY'))
->setConsumerSecret(getenv('MPESA_CONSUMER_SECRET'))
->setEnvironment('sandbox');
$this->mpesa = new Mpesa($config);
}
public function testSuccessfulAuthentication()
{
$this->mpesa->authenticate();
$this->assertNotNull($this->mpesa->getAuth()->getAccessToken());
}
public function testFailedAuthentication()
{
$this->expectException(AuthenticationException::class);
$config = new Config();
$config->setConsumerKey('invalid_key')
->setConsumerSecret('invalid_secret');
$mpesa = new Mpesa($config);
$mpesa->authenticate();
}
}
2. Transaction Tests
// tests/TransactionTest.php
<?php
use PHPUnit\Framework\TestCase;
use MesaSDK\PhpMpesa\Config;
use MesaSDK\PhpMpesa\Mpesa;
class TransactionTest extends TestCase
{
private $mpesa;
protected function setUp(): void
{
$config = new Config();
$config->setBaseUrl("https://apisandbox.safaricom.et")
->setConsumerKey(getenv('MPESA_CONSUMER_KEY'))
->setConsumerSecret(getenv('MPESA_CONSUMER_SECRET'))
->setEnvironment('sandbox');
$this->mpesa = new Mpesa($config);
}
public function testSTKPush()
{
$this->mpesa->authenticate();
$result = $this->mpesa
->setPhoneNumber("251700404709")
->setAmount(10)
->setCallbackUrl("https://test.example.com/callback")
->setTransactionDesc("Test payment")
->setAccountReference("TEST123")
->stkPush();
$this->assertTrue($result->isSuccessful());
$this->assertNotNull($result->getMerchantRequestID());
}
public function testB2C()
{
$this->mpesa->authenticate();
$result = $this->mpesa
->setInitiatorName("apitest")
->setSecurityCredential("test_credential")
->setCommandId("BusinessPayment")
->setAmount(100)
->setPartyA("1020")
->setPartyB("251700404709")
->setRemarks("Test payment")
->setQueueTimeOutUrl("https://test.example.com/timeout")
->setResultUrl("https://test.example.com/result")
->b2c();
$this->assertTrue($result->isSuccessful());
$this->assertNotNull($result->getConversationId());
}
}
Mock Testing
1. Mock HTTP Client
// tests/MockHttpClient.php
<?php
class MockHttpClient implements HttpClientInterface
{
private $responses = [];
private $requests = [];
public function setResponse($url, $response)
{
$this->responses[$url] = $response;
}
public function request($method, $url, $options = [])
{
$this->requests[] = [
'method' => $method,
'url' => $url,
'options' => $options
];
return $this->responses[$url] ?? null;
}
public function getRequests()
{
return $this->requests;
}
}
// Usage in tests
public function testWithMockClient()
{
$mockClient = new MockHttpClient();
$mockClient->setResponse(
'https://apisandbox.safaricom.et/oauth/v1/generate',
['access_token' => 'test_token']
);
$config = new Config();
$config->setHttpClient($mockClient);
$mpesa = new Mpesa($config);
$mpesa->authenticate();
$requests = $mockClient->getRequests();
$this->assertCount(1, $requests);
$this->assertEquals('POST', $requests[0]['method']);
}
2. Mock Logger
// tests/MockLogger.php
<?php
class MockLogger implements LoggerInterface
{
private $logs = [];
public function log($level, $message, array $context = [])
{
$this->logs[] = [
'level' => $level,
'message' => $message,
'context' => $context
];
}
public function getLogs()
{
return $this->logs;
}
}
// Usage in tests
public function testLogging()
{
$mockLogger = new MockLogger();
$config = new Config();
$config->setLogger($mockLogger);
$mpesa = new Mpesa($config);
$mpesa->authenticate();
$logs = $mockLogger->getLogs();
$this->assertNotEmpty($logs);
$this->assertEquals('info', $logs[0]['level']);
}
Test Data
1. Test Fixtures
// tests/fixtures/transaction_data.php
<?php
return [
'successful_stk_push' => [
'ResponseCode' => 0,
'ResponseDesc' => 'Success',
'MerchantRequestID' => '12345-67890-1',
'CheckoutRequestID' => 'ws_CO_123456789',
'CustomerMessage' => 'Success. Your balance is 1000.00'
],
'failed_stk_push' => [
'ResponseCode' => 1,
'ResponseDesc' => 'Failed',
'ErrorMessage' => 'Invalid credentials'
]
];
2. Test Helpers
// tests/helpers/TestHelper.php
<?php
class TestHelper
{
public static function createTestConfig()
{
$config = new Config();
$config->setBaseUrl("https://apisandbox.safaricom.et")
->setConsumerKey(getenv('MPESA_CONSUMER_KEY'))
->setConsumerSecret(getenv('MPESA_CONSUMER_SECRET'))
->setEnvironment('sandbox');
return $config;
}
public static function createTestMpesa()
{
return new Mpesa(self::createTestConfig());
}
}
Running Tests
1. Run All Tests
./vendor/bin/phpunit
2. Run Specific Test File
./vendor/bin/phpunit tests/AuthenticationTest.php
3. Run Specific Test Method
./vendor/bin/phpunit --filter testSuccessfulAuthentication tests/AuthenticationTest.php
Best Practices
1. Test Organization
- Group related tests
- Use descriptive test names
- Follow AAA pattern (Arrange, Act, Assert)
- Keep tests independent
2. Test Coverage
- Test success scenarios
- Test error scenarios
- Test edge cases
- Test validation
3. Test Maintenance
- Keep tests up to date
- Remove obsolete tests
- Document test requirements
- Regular test reviews