Testing
The SDK ships with a layered test suite that you can run yourself when you fork or contribute to the project.
Unit tests
The bulk of the suite is unit tests under test/unit/. They run against in-memory data with no I/O.
bun test # default — runs everything except e2e
bun test test/unit # only unit tests
bun test --coverage # with coverageCoverage threshold is enforced by bunfig.toml:
- ≥ 90 % statements project-wide.
- ≥ 95 % statements under
src/validators/.
The unit suite is the source of the golden tests for the spec-critical pieces:
- Hash algorithm — the three reference hashes from the official v0.1.2 PDF.
- QR URLs — the four example URLs from the v0.5.0 PDF.
- XML round-trip — the four example envelopes from §9 of the SOAP Web Services description.
These tests must never be edited to make a change pass. If they break, fix the implementation.
Integration tests
Under test/integration/. A local mock SOAP server (using undici's interceptor API) replays canned AEAT responses, exercising the full SDK stack short of the actual network:
bun test test/integrationThe mock covers:
- Full acceptance (
Correcto). - Partial acceptance (
ParcialmenteCorrecto) with one rejected record. - Envelope rejection (
Incorrecto). SoapFaultresponses.- Variable
TiempoEsperaEnvioto exercise the flow controller. - Duplicate detection via
IdPeticionRegistroDuplicado.
End-to-end (AEAT pre-production)
The e2e suite lives under test/e2e-aeat/ and is opt-in — it requires a valid pre-production certificate and explicit consent via an environment variable:
VERIFACTU_E2E=1 \
VERIFACTU_CERT_PATH=./cert.pfx \
VERIFACTU_CERT_PASS=changeme \
VERIFACTU_NIF=B12345678 \
bun run test:e2eWithout VERIFACTU_E2E=1, the suite is skipped (the spec calls describe.skipIf so it does not even appear as a failure).
The flow it exercises:
- Register a new invoice (
registerInvoice) — expectsCorrecto+ a CSV. - Wait the
TiempoEsperaEnvioreturned. - Query (
queryInvoices) — confirms the record appears with stateCorrecto. - Cancel (
cancelInvoice) — expectsCorrecto. - Query again — confirms the record state is now
Anulado.
The test uses random seriesNumber values to avoid clashing with itself across runs.
Pre-production certificates are free; request one via the AEAT Sede electrónica (search for "Certificado pruebas TIKE").
Property-based tests
test/unit/properties/ uses fast-check to fuzz the validators that take numeric input (CuotaRepercutida, CuotaTotal, ImporteTotal margins) and the NIF-IVA structure checks. Run them as part of the standard unit suite — they are not separately gated.
Type-level tests
test/types/ uses tsd to pin the inferred types of the public API. Run them with:
bun x tsdThe CI runs this alongside bun test. If you change a public function's signature you must update the matching .test-d.ts file.
Reproducing AEAT responses
If the AEAT changes a response format you need to update the integration fixtures. The recommended flow is:
- Capture the new response from the pre-production environment using
VERIFACTU_DEBUG=1. - Sanitise the payload (strip CSVs and certificate-specific fields).
- Save it under
test/fixtures/responses/. - Wire it into the mock in
test/integration/mock/. - Re-run
bun test test/integrationuntil green.
Continuous integration
The GitHub Actions matrix runs the suite on Ubuntu, Windows and macOS, against Bun 1.1.x and latest. The job fails on:
- Any unit / integration test failure.
- Coverage below the configured threshold.
- Biome lint or typecheck errors.
The e2e suite is not part of CI — the certificate cannot be safely shared with GitHub-hosted runners. Run it locally before tagging a release.
See also
- Validations — the rules verified by the unit suite.
- Error codes — the catalog matched against AEAT responses.