1Test::Roo::Cookbook(3)User Contributed Perl DocumentationTest::Roo::Cookbook(3)
2
3
4
6 Test::Roo::Cookbook - Test::Roo examples
7
9 version 1.004
10
12 This file offers usage ideas and examples for Test::Roo.
13
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
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
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
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
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)