Skip to content

Testing

The package is built on Laravel’s HTTP client, so Http::fake() intercepts every Daraja call. You do not need sandbox credentials, a network, or a phone.

use Illuminate\Support\Facades\Http;
use Starnerz\LaravelDaraja\Facades\Daraja;
it('sends an STK push when an order is placed', function () {
Http::fake([
'*/oauth/*' => Http::response(['access_token' => 'test', 'expires_in' => '3599']),
'*/mpesa/stkpush/*' => Http::response([
'MerchantRequestID' => '29115-34620561-1',
'CheckoutRequestID' => 'ws_CO_191220191020363925',
'ResponseCode' => '0',
'ResponseDescription' => 'Success',
'CustomerMessage' => 'Success',
]),
]);
$this->post('/orders', ['phone' => '0712345678', 'amount' => 1500])
->assertOk();
Http::assertSent(fn ($request) => str_contains($request->url(), 'stkpush')
&& $request['Amount'] === '1500');
});

Always fake the OAuth endpoint too — the package fetches a token before the first call.

it('handles a rejected push', function () {
Http::fake([
'*/oauth/*' => Http::response(['access_token' => 'test']),
'*/mpesa/stkpush/*' => Http::response([
'requestId' => '11728-2929992-1',
'errorCode' => '400.002.02',
'errorMessage' => 'Bad Request - Invalid Amount',
], 400),
]);
expect(fn () => Daraja::stk()->push('0712345678', 0, 'INV-1'))
->toThrow(ApiRequestException::class, 'Invalid Amount');
});

ApiRequestException exposes errorCode, requestId, status and the decoded payload.

Post a real payload at your route and assert on the event:

use Starnerz\LaravelDaraja\Events\StkCallbackReceived;
it('marks the order paid', function () {
Event::fake();
$this->postJson('/daraja/stk', [
'Body' => ['stkCallback' => [
'MerchantRequestID' => '29115-34620561-1',
'CheckoutRequestID' => 'ws_CO_191220191020363925',
'ResultCode' => 0,
'ResultDesc' => 'The service request is processed successfully.',
'CallbackMetadata' => ['Item' => [
['Name' => 'Amount', 'Value' => 1500.0],
['Name' => 'MpesaReceiptNumber', 'Value' => 'NLJ7RT61SV'],
['Name' => 'PhoneNumber', 'Value' => 254712345678],
]],
]],
])->assertOk();
Event::assertDispatched(StkCallbackReceived::class);
});

Test the failure payload too — it has no CallbackMetadata at all:

'Body' => ['stkCallback' => [
'MerchantRequestID' => '…',
'CheckoutRequestID' => '…',
'ResultCode' => 1032,
'ResultDesc' => 'Request cancelled by user',
]],

Http::assertSent() receives the decoded body, which is where the value is:

Http::assertSent(function ($request) {
$body = $request->data();
expect($body['PartyA'])->toBe('254712345678')
->and($body['TransactionType'])->toBe('CustomerPayBillOnline');
return true;
});

The token is cached, so a test asserting request counts should flush first:

beforeEach(fn () => cache()->flush());

Otherwise a token cached by an earlier test makes the OAuth call disappear and your count is off by one.

When you do need the real thing, see Sandbox credentials. Remember that callbacks require a publicly reachable URL even in sandbox.

Laravel Daraja is free and open source, maintained by eazysoft.

An independent open-source project, not affiliated with, endorsed by, or sponsored by Safaricom PLC or Laravel LLC. M-Pesa and Daraja are trademarks of Safaricom PLC; Laravel is a trademark of Laravel LLC. Used here only to say what this package works with.