A practical guide for developers and QA engineers — test OTP delivery, two-factor auth flows, and phone verification without burning through personal numbers or hitting rate limits.
SMS-based 2FA and OTP flows are a common security pattern, but they create a real pain for developers and QA engineers:
Fortunately, there are four clean solutions depending on your context.
Best for: Manual exploratory testing, verifying a sign-up flow works end-to-end, one-off tests.
Free disposable numbers are real VoIP numbers that display their received SMS publicly on a website. You use one as the phone number during sign-up, then read the verification code on the site.
Best for: Automated tests, CI/CD pipelines, end-to-end testing with real flows.
Most major auth/SMS platforms let you register "test" phone numbers that always return a fixed OTP without sending a real SMS.
Register test numbers in the Firebase console → Authentication → Sign-in method → Phone → Allowlist test numbers. Each test number gets a fixed verification code you specify.
// In your test environment
const auth = getAuth();
// Register test number in Firebase console:
// +1 650-555-0000 → code: 123456
await signInWithPhoneNumber(auth, '+16505550000', recaptchaVerifier);
// Then confirm with the fixed code:
await confirmationResult.confirm('123456');
Twilio has a Magic Number program and a test credentials mode. Using test credentials, SMS sends don't actually deliver but return a success response.
# Use Twilio test credentials (no real SMS sent) TWILIO_ACCOUNT_SID=ACtest... # test prefix TWILIO_AUTH_TOKEN=your_test_token # To number +15005550009 always succeeds # From number +15005550006 always succeeds
AWS SNS SMS sandbox mode only delivers to verified destination numbers. Add your test numbers as "sandbox destinations" in the console.
Best for: Unit tests, fast CI runs, testing the OTP validation logic itself.
The cleanest approach for automated testing is to mock the SMS sending layer entirely. Your code should inject the SMS provider, making it swappable in tests.
// PHP / Laravel — mock the SMS channel in tests
public function test_user_can_verify_phone(): void
{
// Intercept all outbound SMS — no real send
Notification::fake();
$response = $this->post('/verify', ['phone' => '+14155550100']);
// Assert the OTP notification was dispatched
Notification::assertSentTo(
User::find(1),
VerifyPhoneNotification::class,
fn ($n) => $n->code === '123456'
);
}
# Python — patch the send_sms function
from unittest.mock import patch
@patch('myapp.sms.send_sms', return_value=True)
def test_otp_flow(mock_send):
response = client.post('/request-otp', {'phone': '+14155550100'})
assert response.status_code == 200
# Retrieve OTP from your test DB / cache and verify
otp = OTPModel.latest_for('+14155550100')
resp2 = client.post('/verify-otp', {'code': otp.code})
assert resp2.status_code == 200
Best for: Testing that your form/API accepts or rejects the right phone number formats, seeding test databases.
When you're testing validation logic (not delivery), you need numbers that pass format checks but aren't assigned to anyone. Our Fake Phone Number Generator creates these for 30+ countries in E.164, international, national, or plain formats.
| Situation | Best approach |
|---|---|
| Quick manual test, one-off verification | Disposable number |
| Automated CI/CD test against real flow | Platform test numbers (Firebase/Twilio) |
| Unit tests / fast test suite | Mock/stub the SMS layer |
| Form validation / DB seeding | Fake number generator |
| Testing a specific service (WhatsApp, Telegram) | Disposable number (try a few, some are blocked) |