Testing
Testing
Testing\InteractsWithTax builds a calculator with the shipped regimes and a rate
map you choose — the package's own suite uses it:
use Cbox\Tax\Testing\InteractsWithTax;
$calc = $this->taxCalculator(['DK' => '25', 'FR' => '20']);
$assessment = $calc->assess($query);
Pass no rates to use the helper's fixed test fixture; those figures are not a
production rate source. Jurisdictions come from the real
laravel-geo repository, so place-of-supply behaviour is exercised for free.
A register for the application container
use Cbox\Tax\Testing\FakeRegister;
$store = sys_get_temp_dir().'/my-tax-test';
FakeRegister::at($store)->rate('eu:DK', '25')->install();
config()->set('tax.register.store', $store);
config()->set('tax.register.version', null);
Set the configuration before resolving the calculator or register from the
container. Use a separate temporary directory per test and remove it afterwards.
FakeRegister supplies invented test data and does not need network access.
The package's full composer qa gate includes the live e2e group. For an offline
iteration, run vendor/bin/pest --exclude-group=e2e; that is a narrower check than
the full gate.
Independent result checks
composer test:reference syncs the release pinned in
conformance/reference/2026-09-17.json, verifies its local hashes and assesses
41 dated reference cases through the application container. Pricing runs with
outbound HTTP blocked. Expected rates and rules come from public tax-authority
sources; monetary expectations are sourced examples or independently derived
arithmetic. The register does not generate them.
The cases cover standard, reduced and zero rates, local US rate components, inclusive pricing, credit notes, reverse charge, a rate-change boundary and a mixed-rate order with delivery. To compare a newer register release against the same dated expectations:
CBOX_TAX_REFERENCE_RELEASE=latest composer test:reference
These checks use supplied US localities and do not independently validate street addresses or geocoding. The corpus records the reference URLs, review dates, inputs and assumptions for reviewing those limits.
Exemptions
The same trait builds buyer exemptions from ISO code strings and asserts exempt outcomes:
$assessment = $calc->assess(new TaxQuery(
// …amount, pricing, place, customer, seller…
exemption: $this->taxExemption(
type: ExemptionType::Resale,
reference: 'CA-RESALE-42',
subdivisions: ['US-CA'], // or countries: ['DK'] for national VAT
),
));
$this->assertExempt($assessment, 'CA-RESALE-42'); // Exempt, tax 0, gross = net, reference present
Testing a product form
FakeRegister::fact() publishes a fact in the release's vocabulary, so a product form
driven by CatalogueAudit::productQuestions() can be tested without the network:
FakeRegister::at(config('tax.register.store'))
->fact('product.isConfectionery', 'Is this confectionery, such as sweets, candy, chocolate or chewing gum?')
->fact('recipient.isCharityServingDisabledPersons', 'Is the buyer a charity providing care for disabled people?', subject: 'recipient')
->install();
Testing address resolution by point
FakeRegister::geometry() publishes a state's polygon layer, so a point lookup can be
tested without the network. Features name the register's own codes; format 3 adds
replaces for a combined area that stands in place of others:
FakeRegister::at(config('tax.register.store'))
->rate('us:TX', '6.25')
->rate('us:TX:CITY-2227001', '1', 'local_component')
->geometry('TX', [[
'type' => 'Feature',
'properties' => ['authority' => 'us:TX:CITY-2227001', 'level' => 'city'],
'geometry' => ['type' => 'Polygon', 'coordinates' => [[[-97.8, 30.2], [-97.7, 30.2], [-97.7, 30.3], [-97.8, 30.3], [-97.8, 30.2]]]],
]])
->install();
Testing the geocoder
The provider binds AddressGeocoder only when tax.geocodio.key is set at boot, so a
test that configures Geocodio later binds it with fakeGeocodio(). It is the real
adapter, built from tax.geocodio.*, against a faked API that answers every request
with a response body or an HTTP status:
config()->set('tax.geocodio.rooftop', true);
$this->fakeGeocodio(['results' => [[
'address_components' => ['country' => 'US', 'state_province' => 'KS'],
'fields' => ['zip4' => ['zip9' => ['66101-3064']]],
]]]);
app(AddressGeocoder::class)->locate($address); // US-KS, locality 66101-3064
$this->fakeGeocodio(500); // the next request fails: null
Requests stay assertable with Http::assertSent(). Outside a test,
GeocodioGeocoder::configured($app, $key) builds the same adapter from the config.