use strict;
use Carp;
use Test::Deep qw(cmp_deeply);
our $VERSION = '0.02';
sub setup : Test(setup) {
my $self = shift;
my $module = $self->get_module_name();
if ( defined $module && $self->create_instance() ) {
my $instance = Test::MockObject::Extends->new($module);
$self->{instance} = $instance;
}
$self->_setup();
return;
}
sub run_test_cases {
my ( $self, $cases ) = @_;
foreach my $case ( @{$cases} ) {
my $hook = $case->{pre_test_hook};
if ( defined $hook && ref $hook eq 'CODE' ) {
&$hook();
}
my $method = $case->{method};
my $res;
my $instance = $self->get_instance();
my $module = $self->get_module_name();
if ( defined $instance
&& $instance->can($method)
&& !$self->run_on_module() )
{
$res = $instance->$method( @{ $case->{params} } );
}
elsif ( defined $module ) {
my $call = $module->can($method);
if ( defined $call ) {
$res = &$call( @{ $case->{params} } );
}
}
my $exp = $case->{exp};
if ( defined $exp && ref $exp eq 'CODE' ) {
$res = &$exp($res);
$exp = 1;
}
cmp_deeply( $res, $exp, $case->{name} );
$hook = $case->{post_test_hook};
if ( defined $hook && ref $hook eq 'CODE' ) {
&$hook();
}
}
return;
}
sub get_instance {
my $self = shift;
return $self->{instance};
}
sub get_module_name {
croak('Module name should be set');
}
sub create_instance {
return 0;
}
sub _setup {
return;
}
sub run_on_module {
my ( $self, $set_value ) = @_;
if ( defined $set_value ) {
$self->{_run_on_module} = $set_value;
}
return $self->{_run_on_module} // 0;
}
1;
=head1 NAME
Test::Class::Simple - simplify your unit tests writing based on Test::Class
=head1 VERSION
version 0.01
=head1 SYNOPSIS
package My::Example;
sub new {
my $class = shift;
$class = ref($class) || $class;
my $self = { _counter => 0 };
bless $self, $class;
return $self;
}
sub increase_counter {
my $self = shift;
$self->{_counter}++;
return $self->{_counter};
}
package My::Example::Test;
use parent qw(Test::Class::Simple);
# setup methods are run before every test method.
sub _setup {
my $self = shift;
# get mocked object of the class that is Test::MockObject::Extends
my $instance = $self->get_instance();
$instance->{_counter} = 100;
return;
}
# Set which class should be mocked
sub get_module_name {
return 'My::Example';
}
# Indicate that instance should be created
sub create_instance {
return 1;
}
# a test method that runs 2 test cases
sub test_counter : Test(2) {
my $self = shift;
my $test_cases = [
{
method => 'increase_counter',
params => [],
exp => 101,
name => 'Increase counter once',
},
{
method => 'increase_counter',
params => [],
exp => 102,
name => 'Increase counter twice',
},
];
$self->run_test_cases($test_cases);
return;
}
later in a nearby .t file
#! /usr/bin/perl
use My::Example::Test;
# run all the test methods in My::Example::Test
My::Example::Test->new()->runtests();
exit 0;
=head1 DESCRIPTION
This is an extension of L<Test::Class|https://metacpan.org/pod/Test::Class> module to implement unit tests in more simple and declarative way.
=head2 Methods
=head3 _setup()
Method that is executed before every test method and is useful for some initializations required for the tests.
=head3 get_instance()
Returns mocked object of the class specified in L<get_module_name()|/get_module_name()>. If L<create_instance()|/create_instance()>
is set to false, returns C<undef> value.
=head3 create_instance()
Can be overridden and must return boolean value. Indicates whether mocked instance should be created.
=head3 get_module_name()
Must be overridden and should return name of the module for which tests should be run.
=head3 run_on_module($set_value)
Sets boolean value that indicates that tests should run against the module rather then the instance of the class.
=head3 run_test_cases($cases)
Accepts arrayref of the test cases described with L<options|/Options> inside hash references and executes them one by one.
=head2 Options
=head3 method
Name of the method that should be executed.
=head3 params
Array reference of the parameters that should be passed to the L<method|/method>
=head3 exp
Can be either data structure or a code reference. For data structure L<cmp_deeply|https://metacpan.org/pod/Test::Deep#cmp_deeply>
will be executed. If code reference is set then result will be passed as a single parameter and will be expected to return
true value if test case was considered as successful.
=head3 name
Name of the test case. Usually shown in the output of test run.
=head3 pre_test_hook
Code reference that will be executed before current test case. E.g. for mocking data for next test case.
=head3 post_test_hook
Code reference that will be executed after current test case. E.g. for unmocking data.
=head1 AUTHOR
Oleksii Kysil
=head1 COPYRIGHT & LICENSE
Copyright 2020 Oleksii Kysil, all rights reserved.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut