File Coverage

blib/lib/Test/Deep/JType.pm
Criterion Covered Total %
statement 86 86 100.0
branch 24 24 100.0
condition 3 4 75.0
subroutine 32 32 100.0
pod 6 6 100.0
total 151 152 99.3


line stmt bran cond sub pod time code
1 2     2   98142 use strict;
  2         15  
  2         60  
2 2     2   10 use warnings;
  2         4  
  2         82  
3             package Test::Deep::JType;
4             # ABSTRACT: Test::Deep helpers for JSON::Typist data
5             $Test::Deep::JType::VERSION = '0.007';
6 2     2   726 use JSON::PP ();
  2         14921  
  2         42  
7 2     2   499 use JSON::Typist ();
  2         5  
  2         43  
8 2     2   12 use Test::Deep 1.126 (); # LeafWrapper, as_test_deep_cmp
  2         40  
  2         59  
9              
10 2     2   12 use Exporter 'import';
  2         3  
  2         649  
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 9     9 1 8704 local $Test::Builder::Level = $Test::Builder::Level + 1;
53 7     7   22823 local $Test::Deep::LeafWrapper = sub { Test::Deep::JType::_String->new(@_) },
54 9         55 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 696 sub jstr { Test::Deep::JType::jstr->new(@_); }
107 4     4 1 2717 sub jnum { Test::Deep::JType::jnum->new(@_); }
108 7     7 1 41 sub jbool { Test::Deep::JType::jbool->new(@_); }
109              
110             my $TRUE = jbool(1);
111             my $FALSE = jbool(0);
112              
113 3     3 1 12 sub jtrue { $TRUE }
114 3     3 1 10 sub jfalse { $FALSE }
115              
116             {
117             package
118             Test::Deep::JType::_String;
119              
120 2     2   997 use Test::Deep::Cmp;
  2         1426  
  2         9  
121              
122             sub init
123             {
124 7     7   52 my $self = shift;
125              
126 7         77 $self->{val} = shift;
127             }
128              
129             sub descend
130             {
131 7     7   118 my $self = shift;
132 7         12 my $got = shift;
133              
134             # Stringify what we got for test output purposes. Otherwise,
135             # string overloading won't be called on $got, and we'll end up
136             # with 'JSON::Typist::String=SCALAR(0x...) in our test output
137 7 100       60 $self->data->{got} = $got . "" if defined $got;
138              
139             # If either is undef but not both this is a failure where
140             # as Test::Deep::String would just stringify the undef,
141             # throw a warning, and pass
142 7 100 75     87 if (defined($got) xor defined($self->{val})) {
143 4         11 return 0;
144             }
145              
146 3         11 return $got eq $self->{val};
147             }
148              
149             sub diag_message
150             {
151 5     5   2793 my $self = shift;
152              
153 5         10 my $where = shift;
154              
155 5         15 return "Comparing $where as a string";
156             }
157             }
158              
159             {
160             package Test::Deep::JType::jstr;
161             $Test::Deep::JType::jstr::VERSION = '0.007';
162             use overload
163             '""' => sub {
164             Carp::confess("can't use valueless jstr() as a string")
165 4 100   4   5111 unless defined ${ $_[0] };
  4         317  
166 1         2 return ${ $_[0] };
  1         11  
167             },
168 2     2   515 fallback => 1;
  2         5  
  2         22  
169              
170 2     2   654 BEGIN { our @ISA = 'JSON::Typist::String'; }
171             sub TO_JSON {
172             Carp::confess("can't use valueless jstr() test as JSON data")
173 2 100   2   3755 unless defined ${ $_[0] };
  2         111  
174 1         3 return "${ $_[0] }";
  1         4  
175             }
176              
177             sub as_test_deep_cmp {
178 3     3   3295 my ($self) = @_;
179 3         35 my $value = $$self;
180 3 100       12 return defined $value ? Test::Deep::all($STRING, Test::Deep::str($value))
181             : $STRING;
182             }
183             }
184              
185             {
186             package Test::Deep::JType::jnum;
187             $Test::Deep::JType::jnum::VERSION = '0.007';
188             use overload
189             '0+' => sub {
190             Carp::confess("can't use valueless jnum() as a number")
191 4 100   4   5105 unless defined ${ $_[0] };
  4         334  
192 1         2 return ${ $_[0] };
  1         12  
193             },
194 2     2   15 fallback => 1;
  2         11  
  2         16  
195              
196 2     2   684 BEGIN { our @ISA = 'JSON::Typist::Number'; }
197             sub TO_JSON {
198             Carp::confess("can't use valueless jnum() test as JSON data")
199 2 100   2   1116 unless defined ${ $_[0] };
  2         206  
200 1         2 return 0 + ${ $_[0] };
  1         3  
201             }
202              
203             sub as_test_deep_cmp {
204 3     3   1080 my ($self) = @_;
205 3         34 my $value = $$self;
206 3 100       12 return defined $value ? Test::Deep::all($NUMBER, Test::Deep::num($value))
207             : $NUMBER;
208             }
209             }
210              
211             {
212             package Test::Deep::JType::jbool;
213             $Test::Deep::JType::jbool::VERSION = '0.007';
214             use overload
215             'bool' => sub {
216             Carp::confess("can't use valueless jbool() as a bool")
217 5 100   5   5651 unless defined ${ $_[0] };
  5         322  
218 2         4 return ${ $_[0] };
  2         8  
219             },
220 2     2   15 fallback => 1;
  2         4  
  2         23  
221              
222             sub TO_JSON {
223             Carp::confess("can't use valueless jbool() test as JSON data")
224 3 100   3   4010 unless defined ${ $_[0] };
  3         116  
225 2 100       4 return ${ $_[0] } ? \1 : \0;
  2         9  
226             }
227              
228             sub new {
229 7     7   17 my ($class, $value) = @_;
230 7         23 bless \$value, $class;
231             }
232              
233             sub as_test_deep_cmp {
234 8     8   5660 my ($self) = @_;
235 8         39 my $value = $$self;
236 8 100       26 return defined $value ? Test::Deep::all($BOOL, Test::Deep::bool($value))
237             : $BOOL;
238             }
239             }
240              
241             1;
242              
243             __END__