File Coverage

blib/lib/Test/Deep/JType.pm
Criterion Covered Total %
statement 85 85 100.0
branch 22 22 100.0
condition 3 4 75.0
subroutine 32 32 100.0
pod 6 6 100.0
total 148 149 99.3


line stmt bran cond sub pod time code
1 2     2   76604 use strict;
  2         12  
  2         48  
2 2     2   9 use warnings;
  2         2  
  2         68  
3             package Test::Deep::JType;
4             # ABSTRACT: Test::Deep helpers for JSON::Typist data
5             $Test::Deep::JType::VERSION = '0.006';
6 2     2   577 use JSON::PP ();
  2         11652  
  2         31  
7 2     2   347 use JSON::Typist ();
  2         4  
  2         51  
8 2     2   11 use Test::Deep 1.126 (); # LeafWrapper, as_test_deep_cmp
  2         32  
  2         45  
9              
10 2     2   10 use Exporter 'import';
  2         3  
  2         484  
11             our @EXPORT = qw( jcmp_deeply jstr jnum jbool jtrue jfalse );
12              
13             #pod =head1 OVERVIEW
14             #pod
15             #pod L is a very useful library for testing data structures.
16             #pod Test::Deep::JType extends it with routines for testing
17             #pod L-annotated data.
18             #pod
19             #pod By default, Test::Deep's C will interpret plain numbers and strings
20             #pod as shorthand for C tests, meaning that the corresponding input
21             #pod data will also need to be a plain number or string. That means that this test
22             #pod won't work:
23             #pod
24             #pod my $json = q[ { "key": "value" } ];
25             #pod my $data = decode_json($json);
26             #pod my $typed = JSON::Typist->new->apply_types( $data );
27             #pod
28             #pod cmp_deeply($typed, { key => "value" });
29             #pod
30             #pod ...because C<"value"> will refuse to match an object. You I wrap each
31             #pod string or number to be compared in C or C respectively, but this
32             #pod can be a hassle, as well as a lot of clutter.
33             #pod
34             #pod C is exported by Test::Deep::JType, and behaves just like
35             #pod C, but plain numbers and strings are wrapped in C tests
36             #pod rather than shallow ones, so they always compare with C.
37             #pod
38             #pod To test that the input data matches the right type, other routines are exported
39             #pod that check type as well as content.
40             #pod
41             #pod =cut
42              
43             #pod =func jcmp_deeply
44             #pod
45             #pod This behaves just like Test::Deep's C but wraps plain scalar and
46             #pod number expectations in C, meaning they're compared with C only,
47             #pod instead of also asserting that the found value must not be an object.
48             #pod
49             #pod =cut
50              
51             sub jcmp_deeply {
52 8     8 1 7095 local $Test::Builder::Level = $Test::Builder::Level + 1;
53 6     6   16689 local $Test::Deep::LeafWrapper = sub { Test::Deep::JType::_String->new(@_) },
54 8         39 Test::Deep::cmp_deeply(@_);
55             }
56              
57             #pod =func jstr
58             #pod
59             #pod =func jnum
60             #pod
61             #pod =func jbool
62             #pod
63             #pod =func jtrue
64             #pod
65             #pod =func jfalse
66             #pod
67             #pod These routines are plain old Test::Deep-style assertions that check not only
68             #pod for data equivalence, but also that the data is the right type.
69             #pod
70             #pod C, C, and C take arguments, which are passed to the non-C
71             #pod version of the test used in building the C-style version. In other words,
72             #pod you can write:
73             #pod
74             #pod jcmp_deeply(
75             #pod $got,
76             #pod {
77             #pod name => jstr("Ricardo"),
78             #pod age => jnum(38.2, 0.01),
79             #pod calm => jbool(1),
80             #pod cool => jbool(),
81             #pod collected => jfalse(),
82             #pod },
83             #pod );
84             #pod
85             #pod If no argument is given, then the wrapped value isn't inspected. C just
86             #pod makes sure the value was a JSON string, without comparing it to anything.
87             #pod
88             #pod C and C are shorthand for C and C,
89             #pod respectively.
90             #pod
91             #pod As long as they've got a specific value to test for (that is, you called
92             #pod C and not C, the tests produced by these routines will
93             #pod serialize via a C-enabled JSON encode into the appropriate
94             #pod types. This makes it convenient to use these routines for building JSON as
95             #pod well as testing it.
96             #pod
97             #pod =cut
98              
99             my $STRING = Test::Deep::obj_isa('JSON::Typist::String');
100             my $NUMBER = Test::Deep::obj_isa('JSON::Typist::Number');
101             my $BOOL = Test::Deep::any(
102             Test::Deep::obj_isa('JSON::XS::Boolean'),
103             Test::Deep::obj_isa('JSON::PP::Boolean'),
104             );
105              
106 4     4 1 678 sub jstr { Test::Deep::JType::jstr->new(@_); }
107 4     4 1 2777 sub jnum { Test::Deep::JType::jnum->new(@_); }
108 7     7 1 34 sub jbool { Test::Deep::JType::jbool->new(@_); }
109              
110             my $TRUE = jbool(1);
111             my $FALSE = jbool(0);
112              
113 3     3 1 9 sub jtrue { $TRUE }
114 3     3 1 8 sub jfalse { $FALSE }
115              
116             {
117             package
118             Test::Deep::JType::_String;
119              
120 2     2   735 use Test::Deep::Cmp;
  2         1129  
  2         8  
121              
122             sub init
123             {
124 6     6   35 my $self = shift;
125              
126 6         61 $self->{val} = shift;
127             }
128              
129             sub descend
130             {
131 6     6   83 my $self = shift;
132 6         8 my $got = shift();
133              
134             # If either is undef but not both this is a failure where
135             # as Test::Deep::String would just stringify the undef,
136             # throw a warning, and pass
137 6 100 75     32 if (defined($got) xor defined($self->{val})) {
138 4         10 return 0;
139             }
140              
141 2         8 return $got eq $self->{val};
142             }
143              
144             sub diag_message
145             {
146 4     4   1763 my $self = shift;
147              
148 4         5 my $where = shift;
149              
150 4         11 return "Comparing $where as a string";
151             }
152             }
153              
154             {
155             package Test::Deep::JType::jstr;
156             $Test::Deep::JType::jstr::VERSION = '0.006';
157             use overload
158             '""' => sub {
159             Carp::confess("can't use valueless jstr() as a string")
160 4 100   4   4155 unless defined ${ $_[0] };
  4         254  
161 1         2 return ${ $_[0] };
  1         9  
162             },
163 2     2   331 fallback => 1;
  2         4  
  2         10  
164              
165 2     2   521 BEGIN { our @ISA = 'JSON::Typist::String'; }
166             sub TO_JSON {
167             Carp::confess("can't use valueless jstr() test as JSON data")
168 2 100   2   3452 unless defined ${ $_[0] };
  2         101  
169 1         2 return "${ $_[0] }";
  1         4  
170             }
171              
172             sub as_test_deep_cmp {
173 3     3   332 my ($self) = @_;
174 3         29 my $value = $$self;
175 3 100       10 return defined $value ? Test::Deep::all($STRING, Test::Deep::str($value))
176             : $STRING;
177             }
178             }
179              
180             {
181             package Test::Deep::JType::jnum;
182             $Test::Deep::JType::jnum::VERSION = '0.006';
183             use overload
184             '0+' => sub {
185             Carp::confess("can't use valueless jnum() as a number")
186 4 100   4   4101 unless defined ${ $_[0] };
  4         254  
187 1         2 return ${ $_[0] };
  1         9  
188             },
189 2     2   11 fallback => 1;
  2         3  
  2         24  
190              
191 2     2   458 BEGIN { our @ISA = 'JSON::Typist::Number'; }
192             sub TO_JSON {
193             Carp::confess("can't use valueless jnum() test as JSON data")
194 2 100   2   962 unless defined ${ $_[0] };
  2         183  
195 1         2 return 0 + ${ $_[0] };
  1         3  
196             }
197              
198             sub as_test_deep_cmp {
199 3     3   3248 my ($self) = @_;
200 3         47 my $value = $$self;
201 3 100       12 return defined $value ? Test::Deep::all($NUMBER, Test::Deep::num($value))
202             : $NUMBER;
203             }
204             }
205              
206             {
207             package Test::Deep::JType::jbool;
208             $Test::Deep::JType::jbool::VERSION = '0.006';
209             use overload
210             'bool' => sub {
211             Carp::confess("can't use valueless jbool() as a bool")
212 5 100   5   4282 unless defined ${ $_[0] };
  5         255  
213 2         3 return ${ $_[0] };
  2         6  
214             },
215 2     2   12 fallback => 1;
  2         3  
  2         10  
216              
217             sub TO_JSON {
218             Carp::confess("can't use valueless jbool() test as JSON data")
219 3 100   3   3272 unless defined ${ $_[0] };
  3         101  
220 2 100       4 return ${ $_[0] } ? \1 : \0;
  2         6  
221             }
222              
223             sub new {
224 7     7   17 my ($class, $value) = @_;
225 7         17 bless \$value, $class;
226             }
227              
228             sub as_test_deep_cmp {
229 8     8   3752 my ($self) = @_;
230 8         37 my $value = $$self;
231 8 100       24 return defined $value ? Test::Deep::all($BOOL, Test::Deep::bool($value))
232             : $BOOL;
233             }
234             }
235              
236             1;
237              
238             __END__