Name

Test::Consumer::Contract - check a reader against the keys its producer promises

Synopsis

A distribution declares what its value object promises, who produces it, who reads it, and why each exception is safe:

use Test::Consumer::Contract qw(run_consumer_contract);

run_consumer_contract(
    source        => 'Widget::Maker',
    value_object  => {
        class   => 'Widget',
        from    => { latitude => 1, longitude => 2 },
        derives => { x => 1, y => 2 },
    },
    guaranteed  => [qw(x y)],
    conditional => { colour => 'only the painted path sets it' },
    producers   => { 'lib/Widget/Maker.pm' => [qw(x y colour)] },
    consumers   => {
        'Widget-User/lib/Widget/User.pm' => {
            may_rely_on => {
                render => { colour => 'defaults to undef on purpose' },
            },
        },
    },
    ignored     => [['Widget-List/', 'reads its own normalised output']],
    accessors   => [qw(x y)],   # documented accessors a consumer may read
    not_accessors => {          # documented, but not a slot of one result
        new             => 'the constructor',
        ACCURACY_LEVELS => 'a class constant',
    },
    inspection  => [qw(can isa DOES)],
);

done_testing();

Then it runs these checks:

Checks 2 and 3 need the sibling distributions that share a workspace; on a CPAN-style install they skip with a diagnostic and only check 1 runs. Check 4 needs the value object's own file, which include puts in reach. This is a static, lexical check: it reads source, it does not run the producer.

Requirements

The caller must load a test module (Test::Most is what this module uses) and call done_testing() after run_consumer_contract(); this module adds subtests but does not end the plan.

New

Takes the declaration above as a list. source and guaranteed are required; every other option has a default:

Unknown options are an error rather than being silently ignored.

Run

Runs the three checks as subtests. Called for you by run_consumer_contract().

Run_Consumer_Contract

my $contract = run_consumer_contract(%declaration);

Builds the contract and runs it, returning the object.

Workspace_Present

True when every distribution named in require_workspace is in the workspace.

Sets_Key

$contract->sets_key($source_text, 'database')

True when the text assigns the key, in any of the spellings a producer might use: key => value, 'key' => value, $obj->{'key'} = value.

Strip

Returns the source with POD and comments replaced by blank space, preserving line numbers.

Reads_of_Results

my @reads = $contract->reads_of_results($stripped_text);

Every ->{key} and ->method() read of a variable that holds a result, as [key, line, kind, sub]. A variable holds one if it is bound from a geocode() or reverse_geocode() call, or if it is the first parameter of a sub that this file calls with such a variable - so a helper that takes the result still counts, whatever it names it. One level of that is followed, which is enough for an adapter; a deeper chain is a sign the read belongs in the consumer's registry entry.

Documented_Accessors

my @names = $contract->documented_accessors($source);

Every name the value object's source documents as part of its interface: its =head2 (and deeper) headings and the public subs it defines. =head1 headings are left out - they are NAME, VERSION, SEE ALSO and the rest of the boilerplate, which document no accessor - and so are the private subs, whose leading underscore keeps them out of the interface. A method the value object documents but implements through AUTOLOAD is found by its heading, which is what makes this the list of what is documented rather than of what is compiled.

Limitations

Author

Nigel Horne, <njh at nigelhorne.com>

Copyright 2026 Nigel Horne.

Usage is subject to the GPL2 licence terms.