1Test::Roo::Cookbook(3)User Contributed Perl DocumentationTest::Roo::Cookbook(3)
2
3
4

NAME

6       Test::Roo::Cookbook - Test::Roo examples
7

VERSION

9       version 1.004
10

DESCRIPTION

12       This file offers usage ideas and examples for Test::Roo.
13

ORGANIZING TEST CLASSES AND ROLES

15   Self-contained test file
16       A single test file could be used for simple tests where you want to use
17       Moo attributes for fixtures that get used by test blocks.
18
19       Here is an example that requires a "corpus" attribute, stores lines
20       from that file in the "lines" attribute and makes it available to all
21       test blocks:
22
23           # examples/cookbook/single_file.t
24
25           use Test::Roo;
26
27           use MooX::Types::MooseLike::Base qw/ArrayRef/;
28           use Path::Tiny;
29
30           has corpus => (
31               is       => 'ro',
32               isa      => sub { -f shift },
33               required => 1,
34           );
35
36           has lines => (
37               is  => 'lazy',
38               isa => ArrayRef,
39           );
40
41           sub _build_lines {
42               my ($self) = @_;
43               return [ map { lc } path( $self->corpus )->lines ];
44           }
45
46           test 'sorted' => sub {
47               my $self = shift;
48               is_deeply( $self->lines, [ sort @{$self->lines} ], "alphabetized");
49           };
50
51           test 'a to z' => sub {
52               my $self = shift;
53               my %letters = map { substr($_,0,1) => 1 } @{ $self->lines };
54               is_deeply( [sort keys %letters], ["a" .. "z"], "all letters found" );
55           };
56
57
58           run_me( { corpus => "/usr/share/dict/words" } );
59           # ... test other corpuses ...
60
61           done_testing;
62
63   Standalone test class
64       You don't have to put the test class into the .t file.  It's just a
65       class.
66
67       Here is the same corpus checking example as before, but now as a class:
68
69           # examples/cookbook/lib/CorpusCheck.pm
70
71           package CorpusCheck;
72           use Test::Roo;
73
74           use MooX::Types::MooseLike::Base qw/ArrayRef/;
75           use Path::Tiny;
76
77           has corpus => (
78               is       => 'ro',
79               isa      => sub { -f shift },
80               required => 1,
81           );
82
83           has lines => (
84               is  => 'lazy',
85               isa => ArrayRef,
86           );
87
88           sub _build_lines {
89               my ($self) = @_;
90               return [ map { lc } path( $self->corpus )->lines ];
91           }
92
93           test 'sorted' => sub {
94               my $self = shift;
95               is_deeply( $self->lines, [ sort @{$self->lines} ], "alphabetized");
96           };
97
98           test 'a to z' => sub {
99               my $self = shift;
100               my %letters = map { substr($_,0,1) => 1 } @{ $self->lines };
101               is_deeply( [sort keys %letters], ["a" .. "z"], "all letters found" );
102           };
103
104           1;
105
106       Running it from a .t file doesn't even need Test::Roo:
107
108           # examples/cookbook/standalone.t
109
110           use strictures;
111           use Test::More;
112
113           use lib 'lib';
114           use CorpusCheck;
115
116           CorpusCheck->run_tests({ corpus => "/usr/share/dict/words" });
117
118           done_testing;
119
120   Standalone Test Roles
121       The real power of Test::Roo is decomposing test behaviors into roles
122       that can be reused.
123
124       Imagine we want to test a file-finder module like Path::Iterator::Rule.
125       We could put tests for it into a role, then run the tests from a file
126       that composes that role.  For example, here would be the test file:
127
128           # examples/cookbook/test-pir.pl
129
130           use Test::Roo;
131
132           use lib 'lib';
133
134           with 'IteratorTest';
135
136           run_me(
137               {
138                   iterator_class => 'Path::Iterator::Rule',
139                   result_type    => '',
140               }
141           );
142
143           done_testing;
144
145       Then in the distribution for Path::Class::Rule, the same role could be
146       tested with a test file like this:
147
148           # examples/cookbook/test-pcr.pl
149
150           use Test::Roo;
151
152           use lib 'lib';
153
154           with 'IteratorTest';
155
156           run_me(
157               {
158                   iterator_class => 'Path::Class::Rule',
159                   result_type    => 'Path::Class::Entity',
160               },
161           );
162
163           done_testing;
164
165       What is the common role that they are consuming?  It sets up a test
166       directory, creates files and runs tests:
167
168           # examples/cookbook/lib/IteratorTest.pm
169
170           package IteratorTest;
171           use Test::Roo::Role;
172
173           use MooX::Types::MooseLike::Base qw/:all/;
174           use Class::Load qw/load_class/;
175           use Path::Tiny;
176
177           has [qw/iterator_class result_type/] => (
178               is       => 'ro',
179               isa      => Str,
180               required => 1,
181           );
182
183           has test_files => (
184               is      => 'ro',
185               isa     => ArrayRef,
186               default => sub {
187                   return [
188                       qw(
189                       aaaa
190                       bbbb
191                       cccc/dddd
192                       eeee/ffff/gggg
193                       )
194                   ];
195               },
196           );
197
198           has tempdir => (
199               is  => 'lazy',
200               isa => InstanceOf ['Path::Tiny']
201           );
202
203           has rule_object => (
204               is      => 'lazy',
205               isa     => Object,
206               clearer => 1,
207           );
208
209           sub _build_description { return shift->iterator_class }
210
211           sub _build_tempdir {
212               my ($self) = @_;
213               my $dir = Path::Tiny->tempdir;
214               $dir->child($_)->touchpath for @{ $self->test_files };
215               return $dir;
216           }
217
218           sub _build_rule_object {
219               my ($self) = @_;
220               load_class( $self->iterator_class );
221               return $self->iterator_class->new;
222           }
223
224           sub test_result_type {
225               my ( $self, $file ) = @_;
226               if ( my $type = $self->result_type ) {
227                   isa_ok( $file, $type, $file );
228               }
229               else {
230                   is( ref($file), '', "$file is string" );
231               }
232           }
233
234           test 'find files' => sub {
235               my $self = shift;
236               $self->clear_rule_object; # make sure have a new one each time
237
238               $self->tempdir;
239               my $rule = $self->rule_object;
240               my @files = $rule->file->all( $self->tempdir, { relative => 1 } );
241
242               is_deeply( \@files, $self->test_files, "correct list of files" )
243               or diag explain \@files;
244
245               $self->test_result_type($_) for @files;
246           };
247
248           # ... more tests ...
249
250           1;
251

CREATING AND MANAGING FIXTURES

253   Skipping all tests
254       If you need to skip all tests in the .t file because some prerequisite
255       isn't available or some fixture couldn't be built, use a "BUILD" method
256       and call "plan skip_all => $reason".
257
258           use Class::Load qw/try_load_class/;
259
260           has fixture => (
261               is => 'lazy',
262           );
263
264           sub _build_fixture {
265               # ... something that might die if unavailable ...
266           }
267
268           sub BUILD {
269               my ($self) = @_;
270
271               try_load_class('Class::Name')
272                   or plan skip_all => "Class::Name required to run these tests";
273
274               eval { $self->fixture }
275                   or plan skip_all => "Couldn't build fixture";
276           }
277
278   Setting a test description
279       You can override "_build_description" to create a test description
280       based on other attributes.  For example, the "IteratorTest" package
281       earlier had these lines:
282
283           has [qw/iterator_class result_type/] => (
284               is       => 'ro',
285               isa      => Str,
286               required => 1,
287           );
288
289           sub _build_description { return shift->iterator_class }
290
291       The "iterator_class" attribute is required and then the description is
292       set to it.  Or, there could be a more verbose description:
293
294           sub _build_description {
295               my $name = shift->iterator_class;
296               return "Testing the $name class"
297           }
298
299   Requiring a builder
300       A test role can specify a lazy attribute and then require the consuming
301       class to provide a builder for it.
302
303       In the test role:
304
305           has fixture => (
306               is => 'lazy',
307           );
308
309           requires '_build_fixture';
310
311       In the consuming class:
312
313           sub _build_fixture { ... }
314
315   Clearing fixtures
316       If a fixture has a clearer method, it can be easily reset during
317       testing.  This works really well with lazy attributes which get
318       regenerated on demand.
319
320           has fixture => (
321               is => 'lazy',
322               clearer => 1,
323           );
324
325           test "some test" => sub {
326               my $self = shift;
327               $self->clear_fixture;
328               ...
329           };
330

MODIFIERS FOR SETUP AND TEARDOWN

332   Setting up a fixture before testing
333       When you need to do some extra work to set up a fixture, you can put a
334       method modifier on the "setup" method.  In some cases, this is more
335       intuitive than doing all the work in an attribute builder.
336
337       Here is an example that creates an SQLite table before any tests are
338       run and cleans up afterwards:
339
340           # example/cookbook/sqlite.t
341
342           use Test::Roo;
343           use DBI;
344           use Path::Tiny;
345
346           has tempdir => (
347               is      => 'ro',
348               clearer => 1,
349               default => sub { Path::Tiny->tempdir },
350           );
351
352           has dbfile => (
353               is      => 'lazy',
354               default => sub { shift->tempdir->child('test.sqlite3') },
355           );
356
357           has dbh => ( is => 'lazy', );
358
359           sub _build_dbh {
360               my $self = shift;
361               DBI->connect(
362                   "dbi:SQLite:dbname=" . $self->dbfile, { RaiseError => 1 }
363               );
364           }
365
366           before 'setup' => sub {
367               my $self = shift;
368               $self->dbh->do("CREATE TABLE f (f1, f2, f3)");
369           };
370
371           after 'teardown' => sub { shift->clear_tempdir };
372
373           test 'first' => sub {
374               my $self = shift;
375               my $dbh  = $self->dbh;
376               my $sth  = $dbh->prepare("INSERT INTO f(f1,f2,f3) VALUES (?,?,?)");
377               ok( $sth->execute( "one", "two", "three" ), "inserted data" );
378
379               my $got = $dbh->selectrow_arrayref("SELECT * FROM f");
380               is_deeply( $got, [qw/one two three/], "read data" );
381           };
382
383           run_me;
384           done_testing;
385
386   Running tests during setup and teardown
387       You can run any tests you like during setup or teardown.  The previous
388       example could have written the setup and teardown hooks like this:
389
390           before 'setup' => sub {
391               my $self = shift;
392               ok( ! -f $self->dbfile, "test database file not created" );
393               ok( $self->dbh->do("CREATE TABLE f (f1, f2, f3)"), "created table");
394               ok( -f $self->dbfile, "test database file exists" );
395           };
396
397           after 'teardown' => sub {
398               my $self = shift;
399               my $dir = $self->tempdir;
400               $self->clear_tempdir;
401               ok( ! -f $dir, "tempdir cleaned up");
402           };
403

MODIFIERS ON TESTS

405   Global modifiers with "each_test"
406       Modifying "each_test" triggers methods before or after every test block
407       defined with the "test" function.  Because this affects all tests,
408       whether from the test class or composed from roles, it needs to be used
409       thoughtfully.
410
411       Here is an example that ensures that every test block is run in its own
412       separate temporary directory.
413
414           # examples/cookbook/with_tempd.t
415
416           use Test::Roo;
417           use File::pushd qw/tempd/;
418           use Cwd qw/getcwd/;
419
420           has tempdir => (
421               is => 'lazy',
422               isa => sub { shift->isa('File::pushd') },
423               clearer => 1,
424           );
425
426           # tempd changes directory until the object is destroyed
427           # and the fixture caches the object until cleared
428           sub _build_tempdir { return tempd() }
429
430           # building attribute will change to temp directory
431           before each_test => sub { shift->tempdir };
432
433           # clearing attribute will change to original directory
434           after each_test => sub { shift->clear_tempdir };
435
436           # do stuff in a temp directory
437           test 'first test' => sub {
438               my $self = shift;
439               is( $self->tempdir, getcwd(), "cwd is " . $self->tempdir );
440               # ... more tests ...
441           };
442
443           # do stuff in a separate, fresh temp directory
444           test 'second test' => sub {
445               my $self = shift;
446               is( $self->tempdir, getcwd(), "cwd is " . $self->tempdir );
447               # ... more tests ...
448           };
449
450           run_me;
451           done_testing;
452
453   Individual test modifiers
454       If you want to have method modifiers on an individual test, put your
455       Test::More tests in a method, add modifiers to that method, and use
456       "test" to invoke it.
457
458           # examples/cookbook/hookable_test.t
459
460           use Test::Roo;
461
462           has counter => ( is => 'rw', default => sub { 0 } );
463
464           sub is_positive {
465               my $self = shift;
466               ok( $self->counter > 0, "counter is positive" );
467           }
468
469           before is_positive => sub { shift->counter( 1 ) };
470
471           test 'hookable' => sub { shift->is_positive };
472
473           run_me;
474           done_testing;
475
476   Wrapping tests
477       As a middle ground between global and individual modifiers, if you need
478       to call some code repeatedly for some, but not all all tests, you can
479       create a custom test function.  This might make sense for only a few
480       tests, but could be helpful if there are many that need similar
481       behavior, but you can't make it global by modifying "each_test".
482
483       The following example clears the fixture before tests defined with the
484       "fresh_test" function.
485
486           # examples/cookbook/wrapped.t
487
488           use strict;
489           use Test::Roo;
490
491           has fixture => (
492               is => 'rw',
493               lazy => 1,
494               builder => 1,
495               clearer => 1,
496           );
497
498           sub _build_fixture { "Hello World" }
499
500           sub fresh_test {
501               my ($name, $code) = @_;
502               test $name, sub {
503                   my $self = shift;
504                   $self->clear_fixture;
505                   $code->($self);
506               };
507           }
508
509           fresh_test 'first' => sub {
510               my $self = shift;
511               is ( $self->fixture, 'Hello World', "fixture has default" );
512               $self->fixture("Goodbye World");
513           };
514
515           fresh_test 'second' => sub {
516               my $self = shift;
517               is ( $self->fixture, 'Hello World', "fixture has default" );
518           };
519
520           run_me;
521           done_testing;
522

AUTHOR

524       David Golden <dagolden@cpan.org>
525
527       This software is Copyright (c) 2013 by David Golden.
528
529       This is free software, licensed under:
530
531         The Apache License, Version 2.0, January 2004
532
533
534
535perl v5.38.0                      2023-07-21            Test::Roo::Cookbook(3)
Impressum