Class-Abstract
view release on metacpan or search on metacpan
};
# Purpose: calling import() again must NOT add a second entry
subtest 'import() -- no duplicate when called on a package that already inherits' => sub {
plan tests => 2;
# UT::ImportA already has Class::Abstract from the previous subtest
my $count_before = scalar grep { $_ eq $CLASS_ABSTRACT } @UT::ImportA::ISA;
# Second import call -- must not change the count
{ package UT::ImportA; Class::Abstract->import() }
my $count_after = scalar grep { $_ eq $CLASS_ABSTRACT } @UT::ImportA::ISA;
# Entry count must be unchanged
is $count_after, $count_before,
'calling import() twice does not duplicate the entry in @ISA';
# Sanity: the count should be exactly one
is $count_after, $TRUE,
'Class::Abstract appears exactly once in @UT::ImportA::ISA';
};
# Purpose: import() must return the module name as a typed string
subtest 'import() -- return value is the module name as a typed string' => sub {
plan tests => 3;
# Install a spy to confirm set_return is called with the right schema.
# The spy passes through to the original, so import() still works.
my $spy = spy 'Class::Abstract::set_return';
# Call import() from a fresh package so the normal (non-early-return) path runs
my $ret;
{ package UT::ImportB; our @ISA = (); $ret = Class::Abstract->import() }
# Restore the spy before any assertion that may call set_return itself
restore_all();
diag "import() returned: '$ret'" if $ENV{TEST_VERBOSE};
# The return value must equal the module name
is $ret, $CLASS_ABSTRACT, 'import() returns the string "Class::Abstract"';
# Validate the return type using the documented schema
returns_ok $ret, $config{schema_string},
'import() return satisfies the { type => "string" } schema';
# Verify set_return was invoked (typed-return contract honoured)
my @calls = $spy->();
ok scalar @calls >= $TRUE,
'set_return() was called inside import() for the typed return';
};
# Purpose: import() must not add anything to Class::Abstract's own @ISA
subtest 'import() -- does not register Class::Abstract into its own @ISA' => sub {
plan tests => 1;
# Snapshot the ISA array before any call
my @snapshot = @Class::Abstract::ISA;
# Re-entering import() from the same module is blocked by the self-guard.
# We verify the invariant directly: ISA must be identical before and after.
is_deeply \@Class::Abstract::ISA, \@snapshot,
'@Class::Abstract::ISA is unchanged (self-registration guard holds)';
};
# ===========================================================================
# SECTION: new()
#
# POD contract:
# - Returns a blessed empty hashref of class $class
# - Croaks for directly abstract classes (when enforcement is active)
# - Accepts a blessed object as the invocant
# - Rejects unblessed references with an error
# - Rejects undef with an error
# - Bypass variables suppress the abstract croak
# ===========================================================================
# Purpose: a concrete class must be successfully instantiated
subtest 'new() -- returns a blessed hashref for a concrete class' => sub {
plan tests => 2;
my $obj;
# Enforcement on: UT::Concrete is not abstract, so new() must succeed
enforcement_on {
lives_ok { $obj = UT::Concrete->new() }
'UT::Concrete->new() lives (concrete class)';
};
# The returned value must be a blessed reference
ok blessed($obj), 'new() returns a blessed reference';
};
# Purpose: the object must carry the correct class, not a base class
subtest 'new() -- blesses the object into the concrete invocant class' => sub {
plan tests => 1;
# ref() on the returned object must equal the calling class
my $obj = UT::Concrete->new();
is ref($obj), $config{pkg_concrete},
'new() blesses the object into UT::Concrete, not Class::Abstract';
};
# Purpose: directly abstract class must croak when enforcement is active
subtest 'new() -- croaks for a directly abstract class (enforcement on)' => sub {
plan tests => 2;
enforcement_on {
# throws_ok verifies exact error message format from the POD
throws_ok { UT::Abstract->new() }
qr/Cannot instantiate abstract class UT::Abstract directly/,
'new() croaks with the exact documented error for abstract class';
# Verify the canonical phrase is present in $@
like $@, qr/Cannot instantiate abstract class/,
'$@ contains the documented error phrase';
};
};
# Purpose: $BYPASS = truthy must suppress the abstract-class croak
( run in 1.140 second using v1.01-cache-2.11-cpan-800906f7e73 )