DEV Community

David
David

Posted on

IBAN Testing for Developers: How to Generate Valid Test IBANs Without Breaking Production

The Problem Every Fintech Developer Knows

You're building a payment form. Or integrating a banking API. Or writing tests for a money transfer feature. And you need IBANs — lots of them.

So what do you do? Copy your own bank account number into test code? (Please don't.) Use DE00000000000000000000 and hope for the best? Google "test IBAN" and get the same three examples everyone else uses?

There's a better way.

What Makes an IBAN Valid?

Before generating test data, let's understand the structure. An IBAN (International Bank Account Number) consists of:

  1. Country code — 2 letters (e.g., DE for Germany, ES for Spain)
  2. Check digits — 2 digits calculated via MOD-97 (ISO 7064)
  3. BBAN — Bank/branch codes + account number (format varies by country)

Here's the anatomy of a German IBAN:

DE89 3704 0044 0532 0130 00
│  │  │         │
│  │  │         └─ Account number
│  │  └─ Bank code (Bankleitzahl)
│  └─ Check digits (89)
└─ Country code (Germany)
Enter fullscreen mode Exit fullscreen mode

The check digits are the magic. They're calculated using the entire BBAN plus the country code, making it mathematically impossible to have a "valid-looking" IBAN that doesn't pass checksum validation. This is why you can't just make up random numbers.

The MOD-97 Algorithm

Here's how IBAN validation works under the hood:

function validateIBAN(iban) {
  // 1. Remove spaces and uppercase
  const clean = iban.replace(/\s/g, ''). toUpperCase();

  // 2. Move first 4 chars to the end
  const rearranged = clean.slice(4) + clean.slice(0, 4);

  // 3. Convert letters to numbers (A=10, B=11, ... Z=35)
  const numeric = rearranged.replace(/[A-Z]/g, 
    ch => ch.charCodeAt(0) - 55
  );

  // 4. MOD 97 must equal 1
  return mod97(numeric) === 1n;
}

function mod97(numStr) {
  // BigInt handles the large numbers
  return BigInt(numStr) % 97n;
}
Enter fullscreen mode Exit fullscreen mode

This means generating a valid IBAN isn't just picking random digits — you need to:

  • Follow the correct BBAN format for the target country
  • Calculate the check digits correctly
  • Use valid bank codes (if you want realistic data)

Why You Need Proper Test IBANs

1. Form Validation Testing

Most payment forms validate IBANs client-side. If your test data doesn't pass MOD-97, you'll never reach the actual API integration you're trying to test.

2. Multi-Country Support

IBAN formats differ wildly by country:

  • 🇩🇪 Germany: 22 characters
  • 🇬🇧 UK: 22 characters (different BBAN structure)
  • 🇪🇸 Spain: 24 characters
  • 🇫🇷 France: 27 characters
  • 🇲🇹 Malta: 31 characters

If you're testing international support, you need valid IBANs from multiple countries.

3. Unit Test Coverage

Hardcoding one or two IBANs in your tests is a code smell. You want:

  • Valid IBANs from different countries
  • Edge cases (maximum length, minimum length)
  • Bulk generation for load testing

4. Staging/Demo Environments

You need realistic-looking data that won't accidentally trigger real transactions.

Generating Test IBANs

I built Random IBAN Generator specifically for this. It generates structurally valid IBANs with correct check digits for 80+ countries.

What it does:

  • Generates IBANs with valid MOD-97 checksums
  • Supports 80+ country formats
  • Lets you pick specific countries or go random
  • Bulk generation (need 50 German IBANs? Done.)
  • Everything runs client-side — no data leaves your browser

What it doesn't do:

  • These are NOT real bank accounts
  • They will pass format/checksum validation
  • They will NOT pass bank-level verification (the bank codes may not correspond to real banks)

This is exactly what you want for testing: data that gets past your validation layer without being connected to real money.

Building IBAN Validation Into Your App

Here's a production-ready validation function:

const IBAN_LENGTHS = {
  AL: 28, AD: 24, AT: 20, AZ: 28, BH: 22,
  BY: 28, BE: 16, BA: 20, BR: 29, BG: 22,
  CR: 22, HR: 21, CY: 28, CZ: 24, DK: 18,
  DO: 28, TL: 23, EE: 20, FO: 18, FI: 18,
  FR: 27, GE: 22, DE: 22, GI: 23, GR: 27,
  GL: 18, GT: 28, HU: 28, IS: 26, IQ: 23,
  IE: 22, IL: 23, IT: 27, JO: 30, KZ: 20,
  XK: 20, KW: 30, LV: 21, LB: 28, LI: 21,
  LT: 20, LU: 20, MT: 31, MR: 27, MU: 30,
  MC: 27, MD: 24, ME: 22, NL: 18, MK: 19,
  NO: 15, PK: 24, PS: 29, PL: 28, PT: 25,
  QA: 29, RO: 24, SM: 27, SA: 24, RS: 22,
  SK: 24, SI: 19, ES: 24, SE: 24, CH: 21,
  TN: 24, TR: 26, UA: 29, AE: 23, GB: 22,
  VG: 24
};

function isValidIBAN(iban) {
  const clean = iban.replace(/\s/g, '\).toUpperCase();
  const country = clean.slice(0, 2);

  // Check country and length
  if (!IBAN_LENGTHS[country]) return false;
  if (clean.length !== IBAN_LENGTHS[country]) return false;

  // Check format (letters + digits)
  if (!/^[A-Z]{2}\d{2}[A-Z0-9]+$/.test(clean)) return false;

  // MOD-97 check
  const rearranged = clean.slice(4) + clean.slice(0, 4);
  const numeric = rearranged.replace(/[A-Z]/g, 
    ch => (ch.charCodeAt(0) - 55).toString()
  );

  return BigInt(numeric) % 97n === 1n;
}
Enter fullscreen mode Exit fullscreen mode

Testing Strategy

Here's how I'd structure IBAN tests:

describe('IBAN Validation', () => {
  // Generate test data from randomiban.co
  const validIBANs = [
    { country: 'DE', iban: 'DE89370400440532013000' },
    { country: 'GB', iban: 'GB29NWBK60161331926819' },
    { country: 'FR', iban: 'FR7630006000011234567890189' },
    { country: 'ES', iban: 'ES9121000418450200051332' },
  ];

  validIBANs.forEach(({ country, iban }) => {
    test(`accepts valid ${country} IBAN`, () => {
      expect(isValidIBAN(iban)).toBe(true);
    });
  });

  test('rejects wrong check digits', () => {
    expect(isValidIBAN('DE00370400440532013000')).toBe(false);
  });

  test('rejects wrong length', () => {
    expect(isValidIBAN('DE8937040044053201300')).toBe(false);
  });

  test('handles spaces', () => {
    expect(isValidIBAN('DE89 3704 0044 0532 0130 00')).toBe(true);
  });
});
Enter fullscreen mode Exit fullscreen mode

TL;DR

  • Don't hardcode fake IBANs — use properly generated ones with valid checksums
  • IBAN validation = country code + length check + MOD-97
  • Different countries have wildly different BBAN formats
  • Use randomiban.co to generate test IBANs in bulk
  • Client-side generation means your test data never hits a server

The full IBAN spec is defined in ISO 13616. The validation algorithm is ISO 7064 (MOD 97-10). If you're building anything that touches bank accounts, understanding these is non-negotiable.


Part of the Developer Tools Deep Dives series. I'm building free, no-signup developer tools at namso.io and related sites. All client-side, all open.

Top comments (0)