How to handle mocking roles in the Muse?

Say that I have two roles: Simple :: Tax and Real :: Tax. In test situations, I want to use Simple :: Tax, and in production I want to use Real :: Tax. What is the best way to do this? My first thought was to use different versions of the new method to create objects with different roles:

 #!/usr/bin/perl use warnings; { package Simple::Tax; use Moose::Role; requires 'price'; sub calculate_tax { my $self = shift; return int($self->price * 0.05); } } { package A; use Moose; use Moose::Util qw( apply_all_roles ); has price => ( is => "rw", isa => 'Int' ); #price in pennies sub new_with_simple_tax { my $class = shift; my $obj = $class->new(@_); apply_all_roles( $obj, "Simple::Tax" ); } } my $o = A->new_with_simple_tax(price => 100); print $o->calculate_tax, " cents\n"; 

My second thought was to use an if statement in the package body to use different with statements:

 #!/usr/bin/perl use warnings; { package Complex::Tax; use Moose::Role; requires 'price'; sub calculate_tax { my $self = shift; #pretend this is more complex return int($self->price * 0.15); } } { package Simple::Tax; use Moose::Role; requires 'price'; sub calculate_tax { my $self = shift; return int($self->price * 0.05); } } { package A; use Moose; has price => ( is => "rw", isa => 'Int' ); #price in pennies if ($ENV{TEST_A}) { with "Simple::Tax"; } else { with "Complex::Tax"; } } my $o = A->new(price => 100); print $o->calculate_tax, " cents\n"; 

One of them is better than the other, is there anything terrible about any of them, and there is a better way that I have not thought of yet.

+4
source share
1 answer

My first suggestion would be something like MooseX::Traits , and then specify different roles when creating the object:

 my $test = A->with_traits('Simple::Tax')->new(...); my $prod = A->with_traits('Complex::Tax')->new(...); 

But this opens the door to creating A without a role. Therefore, thinking about this further, I think you have an X / Y problem. If Simple::Tax used only for modeling Complex::Tax in a test environment, you can do several things to override the implementation of Complex :: Tax.

For example, you can simply define Simple :: Tax as follows:

 package Simple::Tax; use Moose::Role; requires 'calculate_tax'; around calculate_tax => sub { int($_[1]->price * 0.05) }; 

Then there is always A compose Complex::Tax and apply Simple :: Tax to it only during the tests (using apply_all_roles ).

If you need Simple :: Tax and Complex :: Tax as in production (and not just for testing), your best choice is refactoring from the relationship of the composition (is) to the relationship with the delegation (is).

  package TaxCalculator::API; use Moose::Role; requires qw(calculate_tax); package SimpleTax::Calculator; use Moose; with qw(TaxCalculator::API); sub calculate_tax { ... } package ComplexTax::Calculator; use Moose; with qw(TaxCalculator::API); sub calcuate_tax { ... } package A; use Moose; has tax_calculator => ( does => 'TaxCalculator::API', handles => 'TaxCalculator::API', default => sub { ComplexTax::Calculator->new() }, ); 

Then, if you want to override it, you simply pass in a new tax_calculator :

 my $test = A->new(tax_calculator => SimpleTax::Calculator->new()); my $prod = A->new(tax_calculator => ComplexTax::Calculator->new()); 

Since handles delegates all methods from the role as new proxies, this is almost identical to composing the role on its own.

+5
source

Source: https://habr.com/ru/post/1392976/


All Articles