Skip to content

Commit

Permalink
Merge pull request #376 from perladvent/2023-12-15
Browse files Browse the repository at this point in the history
2023 12 15
  • Loading branch information
oalders authored Dec 14, 2023
2 parents 04723e3 + 03eae65 commit fcc19b3
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ But writing good tests can sometimes be a coal in the stocking. Some things are

Well, not anymore. This year we updated all our test code to use the magical L<Test2-Suite|https://metacpan.org/pod/Test2::V0>. A single distribution that updates and replaces not just Test::More, but many other testing modules. And the best part? It will be a core distribution in Perl 5.40 onwards! Talk about a Christmas Miracle <3

The basics work pretty much like Test::More, so if you’re used to it you’ll feel right at home. In fact, if you’re not doing anything fancy you can probably replace C<< use Test::More >> with C<< use Test2::V0 >> and everything will work just fine. Check it out:
The basics work pretty much like Test::More, so if you’re used to it you’ll feel right at home. In fact, if you’re not doing anything fancy you can probably replace "C<< use Test::More >>" with "C<< use Test2::V0 >>" and everything will work just fine. Check it out:

#!vim perl
use Test2::V0;

use Acme::Christmas;
Expand All @@ -36,7 +37,7 @@ The basics work pretty much like Test::More, so if you’re used to it you’ll
like $xmas->carol, qr/Merry/, ‘found the proper lyrics’;
}

done_testing; # you can also plan N; if you prefer to count your tests.
done_testing; # you can also "plan N;" if you prefer to count your tests.

But you’ll also get some nice cranberry sauce right out of the box.
For starters, C<strict> and C<warnings> are on by default (and don’t worry,
Expand All @@ -46,6 +47,7 @@ Now you can! Test2::V0’s C<is()>, C<ok()>, C<like()> and most other test
functions support extra arguments I<after the test description>, so you
can write them as:

#!vim perl
ok $xmas->ready, "test if christmas is ready"
=> "hmm... this was not supposed to fail. Let's see..."
. " the tree is " . $xmas->tree
Expand All @@ -54,14 +56,15 @@ can write them as:

and all that extra output will be printed only if the test fails. Ho! Ho! Ho!

=head2 The all-powerful “is” and like
=head2 The all-powerful "is" and "like"

This is straight out my favorite feature. You may have noticed I did not
include C<is_deeply()> on the list of compatibility with Test::More.
include "C<is_deeply()>" on the list of compatibility with Test::More.
Well, that’s because there is no need for it. That’s right! If the variable
you’re testing is a data structure, you can simply use C<is()> and it will
do a deep check, failing if values don’t match or if anything is missing:

#!vim perl
is $recipe, {
name => 'Fruitcake',
ingredients => {
Expand All @@ -78,6 +81,7 @@ It even lets you mix and match between exact values (by passing a string
or a number) and values that match a regular expression (by providing
the regexp).

#!vim perl
like $recipe, {
name => qr(cake)i, # must contain ‘cake’ (case insensitive)
ingredients => {
Expand All @@ -95,6 +99,7 @@ make sure it has no key called ‘microwave’. To do all that, we just write
a very simple definition of our partial hash containing only the bits we
care about:

#!vim perl
# import everything we use in this test
# (the :DEFAULT label is to ensure all regular symbols are also imported)
use Test2::V0 qw( :DEFAULT hash field bag item etc L DNE );
Expand All @@ -114,15 +119,17 @@ If that wasn’t impressive enough, here are some extra nice ways to make your t

=head2 Test if loading a module imports (or doesn’t import) a function or a variable:

#!vim perl
use Some::Module;
imported_ok 'mysub', '$myvar', '@myothervar';
not_imported_ok 'othersub', '$othervar';
```

=head2 Test if something warns or dies / raises an exception

#!vim perl
like dies { … }, qr/some error/, ‘got expected exception from block’;
ok lives { … }, ‘code lived!’, oh, noes! Died with error ‘$@’;
ok lives { … }, ‘code lived!’, "oh, noes! Died with error ‘$@’";

ok warns { … }, ‘at least one warning was issued in the block’;
is warns { … }, 2, ‘got the right number of warnings in block’;
Expand All @@ -136,16 +143,19 @@ If that wasn’t impressive enough, here are some extra nice ways to make your t

If you add this to the beginning of your test file, it will die and stop testing that file as soon as any test on that file fails:

#!vim perl
use Test2::Plugin::DieOnFail;

If you’re running a bunch of different test files, it will not stop testing altogether, just that particular file. To truly bail out of all testing as soon as any test on a file fails, do this instead:

#!vim perl
use Test2::Plugin::BailOnFail;

If you just want to bail on a single test in the file, use "C<< ... or bail_out($reason) >>" after the test.

=head2 Skip tests unless we have a specific perl or module version available:

#!vim perl
# skip all tests in file unless perl is v5.38 or greater:
use Test2::Require::Perl 'v5.38';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Greetings, festive readers! Santa Claus here, ready to share yet another
behind-the-scenes look at how we keep Christmas joyfully on track with
the Magical Test2 Suite, soon to be a core test library in Perl 5.40 onwards.

If you haven't read part 1, it's a real treat!
If you haven't read L<part 1|https://perladvent.org/2023/2023-12-15.html>, it's a real treat!

Just like the tinsel on your tree, mocks add that extra sparkle to our
unit tests. Mocks can help us simulate certain behaviors, creating a
Expand All @@ -21,18 +21,21 @@ And with the Test 2 Suite, this has never been easier!

For starters, let’s say you want to create an object that will be passively called by whatever it is you’re testing:

#!vim perl
use Test2::V0;

my $obj = mock;

Now, C<$obj> is a dud that will accept any method calls and return C<undef>. What about if you want it to return something specific?

#!vim perl
my $obj = mock { merry => 'christmas!' };

is $obj->merry, 'christmas!', 'my mock works';

When you need to test chained calls, you can always nest your mocks():

#!vim perl
my $obj = mock {
happy => mock {
holidays => mock {
Expand All @@ -43,8 +46,9 @@ When you need to test chained calls, you can always nest your mocks():

is $obj->happy->holidays->everyone, 'Ho! Ho! Ho!', 'chained mocks';

I<But Santa>, you may ask, I<< What about when the class is instantiated and used somewhere inside the code I want to test? How can I use mocks to add or override methods to something out of my hands? >> No worries! Just expand your mock definition for more control:
I<"But Santa">, you may ask, I<< "What about when the class is instantiated and used somewhere inside the code I want to test? How can I use mocks to add or override methods to something out of my hands?" >> No worries! Just expand your mock definition for more control:

#!vim perl
my $mock_meta = mock 'Some::Class' => (
track => true,
override => [
Expand All @@ -56,10 +60,11 @@ I<“But Santa”>, you may ask, I<< “What about when the class is instantiate
],
);

And now, as long as C<$mock_meta> exists, any instances of C<Some::Class> will have the mocked behavior. To restore them back to the original, simply undefine that C<$mock_meta> variable or call C<< $mock_meta->reset_all() >>.
And now, as long as C<$mock_meta> exists, any instances of "C<Some::Class>" will have the mocked behavior. To restore them back to the original, simply undefine that C<$mock_meta> variable or call C<< $mock_meta->reset_all() >>.

One thing you may have noticed in the example above is the C<< track => true >>. That means C<$mock_meta> will contain a lot of information ready for you to inspect regarding how many times any given method was called, and which arguments were used. This is particularly important to check if the code you’re testing is properly invoking the mocked class.
One thing you may have noticed in the example above is the "C<< track => true >>". That means C<$mock_meta> will contain a lot of information ready for you to inspect regarding how many times any given method was called, and which arguments were used. This is particularly important to check if the code you’re testing is properly invoking the mocked class.

#!vim perl
test_something_that_uses_my_mocked_class();
is(
$mock_meta->sub_tracking->{some_method}->@*,
Expand All @@ -70,15 +75,16 @@ One thing you may have noticed in the example above is the “C<< track => true
like(
$mock_meta->sub_tracking->{some_method}[0]{args},
[ qr(), ‘arg1’, qr(arg2) ],
'testing call arguments for method some_method'
'testing call arguments for method "some_method"'
);

$mock_meta->clear_sub_tracking(); # so we can go again in isolation


Note that to get the mock metadata from a given variable holding an object, you can also do:

my ($mock_meta) = mocked $actual_obj;
#!vim perl
my ($mock_meta) = mocked $actual_obj;


=head2 Mocking the Christmas Wishlist Database
Expand All @@ -87,6 +93,7 @@ For a real world example, let's see how the elves use Test2 Suite's
mocking features to test our gift delivery system. The code looks
roughly like this:

#!vim perl
sub deliver_gift ($child_name, $gift_name) {
my $schema = GiftDB::Schema->get_active_connection();
my $wish = $schema->resultset('Wishlist')->single({
Expand All @@ -106,6 +113,7 @@ child's wishlist entry. If we have it, we update the entry to 'delivered'.
But how do we test it without actually using a database? We mock the
database, that's how!

#!vim perl
use builtin qw( true false weaken );
use Test2::V0;

Expand Down

0 comments on commit fcc19b3

Please sign in to comment.