File Coverage

blib/lib/DBD/SimpleMock.pm
Criterion Covered Total %
statement 80 82 97.5
branch 22 24 91.6
condition 2 3 66.6
subroutine 21 23 91.3
pod 0 9 0.0
total 125 141 88.6


line stmt bran cond sub pod time code
1 4     4   25 use strict;
  4         4  
  4         123  
2 4     4   10 use warnings;
  4         4  
  4         808  
3              
4             our $VERSION = '0.01';
5              
6             # built out from DBD::Nullp;
7             {
8             package DBD::SimpleMock;
9              
10             require DBI;
11             require Carp;
12              
13             our @EXPORT = qw(); # Do NOT @EXPORT anything.
14             our $VERSION = "0.01";
15              
16             our $drh = undef; # holds driver handle once initialised
17              
18             sub driver {
19 6 100   6 0 886 return $drh if $drh;
20 4         8 my ($class, $attr) = @_;
21 4         7 $class .= "::dr";
22 4         20 ($drh) = DBI::_new_drh($class, {
23             'Name' => 'SimpleMock',
24             'Version' => $VERSION,
25             'Attribution' => 'Mock DBD for tests',
26             });
27 4         166 $drh;
28             }
29              
30             sub CLONE {
31 0     0   0 undef $drh;
32             }
33             }
34              
35              
36             { package DBD::SimpleMock::dr;
37             our $imp_data_size = 0;
38 4     4   18 use strict;
  4         5  
  4         539  
39              
40             sub connect {
41 10     10 0 767 my $drh = shift;
42 10 100       30 if (SimpleMock::Model::DBI::_get_dbi_meta('connect_fail')) {
43             # If connect_fail is set, we simulate a connection failure
44 2         17 $drh->set_err(1000, "Database unavailable");
45 2         11 return;
46             }
47 8 50       26 my $dbh = $drh->SUPER::connect(@_)
48             or return $drh->set_err(1000, "Database unavailable");
49 8         362 $dbh->STORE(Active => 1);
50 8         20 return $dbh;
51             }
52              
53 0     0   0 sub DESTROY { undef }
54             }
55              
56              
57             { package DBD::SimpleMock::db;
58 4     4   16 use strict;
  4         5  
  4         88  
59 4     4   18 use DBI;
  4         4  
  4         250  
60 4     4   14 use base 'DBD::_::db';
  4         5  
  4         1290  
61 4     4   20 use Carp qw(croak);
  4         4  
  4         1297  
62              
63             our $imp_data_size = 0;
64              
65             # Added get_info to support tests in 10examp.t
66             sub get_info {
67 2     2 0 767 my ($dbh, $type) = @_;
68              
69 2 100       10 if ($type == 29) {
70 1         33 return '"';
71             }
72 1         6 return;
73             }
74              
75             sub prepare {
76 18     18 0 5139 my ($dbh, $statement)= @_;
77              
78             # fail if prepare_fail META tag is set
79 18 100       62 if (SimpleMock::Model::DBI::_get_dbi_meta('prepare_fail')) {
80 1         46 return $dbh->set_err(1001, "Prepare failed");
81             }
82              
83 17         63 my ($outer) = DBI::_new_sth($dbh, {
84             'Statement' => $statement,
85             });
86              
87 17         578 return $outer;
88             }
89              
90             sub FETCH {
91 1     1   89 my ($dbh, $attrib) = @_;
92 1         14 return $dbh->SUPER::FETCH($attrib);
93             }
94              
95             sub STORE {
96 50     50   543 my ($dbh, $attrib, $value) = @_;
97 50 100       86 if ($attrib eq 'AutoCommit') {
98 9 100       32 Carp::croak("Can't disable AutoCommit") unless $value;
99             # convert AutoCommit values to magic ones to let DBI
100             # know that the driver has 'handled' the AutoCommit attribute
101 8 50       23 $value = ($value) ? -901 : -900;
102             }
103 49         200 return $dbh->SUPER::STORE($attrib, $value);
104             }
105              
106 1     1 0 6 sub ping { 1 }
107              
108             sub disconnect {
109 1     1 0 41 shift->STORE(Active => 0);
110             }
111              
112             }
113              
114              
115             { package DBD::SimpleMock::st; # ====== STATEMENT ======
116             our $imp_data_size = 0;
117 4     4   17 use strict;
  4         4  
  4         1444  
118              
119             sub bind_param {
120 2     2 0 14 my ($sth, $param, $value, $attr) = @_;
121 2         8 $sth->{ParamValues}{$param} = $value;
122 2 100       8 if (defined $attr) {
123             # attr (SQL type hints) accepted but not used by mock driver
124             }
125 2         12 return 1;
126             }
127              
128             sub execute {
129 16     16 0 490 my ($sth, @arg) = @_;
130 16 100       37 if (SimpleMock::Model::DBI::_get_dbi_meta('execute_fail')) {
131 1         77 return $sth->set_err(1002, "Execute failed");
132             }
133 15         48 my $mock = SimpleMock::Model::DBI::_get_mock_for($sth->{Statement}, \@arg);
134 14         49 my $field_count = @{$mock->{data}->[0]};
  14         30  
135 14         69 $sth->STORE(NUM_OF_FIELDS => $field_count);
136 14         45 $sth->STORE(Active => 1);
137 14         23 $sth->{simplemock_data} = $mock->{data};
138 14 100       40 $sth->{NAME} = $mock->{cols} if ($mock->{cols});
139 14         50 1;
140             }
141              
142             sub fetchrow_arrayref {
143 28     28 0 540 my $sth = shift;
144              
145 28         27 my $data = shift @{$sth->{simplemock_data}};
  28         40  
146 28 100 66     77 if (!$data || !@$data) {
147 9         36 $sth->finish; # no more data so finish
148 9         51 return;
149             }
150 19         108 return $sth->_set_fbav($data);
151             }
152              
153             *fetch = \&fetchrow_arrayref;
154              
155             sub FETCH {
156 1     1   19 my ($sth, $attrib) = @_;
157 1         33 return $sth->SUPER::FETCH($attrib);
158             }
159              
160             sub STORE {
161 28     28   42 my ($sth, $attrib, $value) = @_;
162 28         90 return $sth->SUPER::STORE($attrib, $value);
163             }
164              
165             }
166              
167             1;
168              
169             =head1 NAME
170              
171             DBD::SimpleMock - A mock DBD for testing DBI applications
172              
173             =head1 SYNOPSIS
174              
175             Generally, you will use this directly via the SimpleMock module, but see the test
176             for a standalone usage example.
177              
178             use SimpleMock qw(register_mocks);
179             use Module::To::Test;
180             my $d1 = [
181             [ 'Clive', 'clive@testme.com' ],
182             [ 'Colin', 'colin@testme.com' ],
183             ];
184              
185             my $d2 = [
186             [ 'Dave', 'dave@testme.com' ],
187             [ 'Diane', 'diane@testme.com' ],
188             ];
189              
190             register_mocks(
191             DBI => {
192             # see below for global flags that can be set - by default they are all 0
193             META => {
194             allow_unmocked_queries => 0, # Set to 1 to allow unmocked queries to not be fatal
195             connect_fail => 0, # Set to 1 to simulate a connection failure
196             prepare_fail => 0, # Set to 1 to simulate a prepare failure
197             execute_fail => 0, # Set to 1 to simulate an execute failure
198             },
199             QUERIES => [
200             {
201             sql => 'SELECT id, name, email FROM my_table WHERE name LIKE ?',
202             # each query can have multiple results defined based on placeholder values
203             # note: the C%/D% in the args are literals on the actual args sent
204             # the code may be using a wild card, but the mocks are not aware of that
205             results => [
206             { args => [ 'C%' ], data => $d1 },
207             { args => [ 'D%' ], data => $d2 },
208             ],
209             # define cols if using any of the *_hashref methods
210             cols => [ 'id', 'name', 'email' ],
211             },
212             ],
213             }
214             );
215              
216             # call the code that runs the query
217             my $result = Module::To::Test->get_data('C%');
218             is_deeply $result, $d1, 'Got expected data for C%';
219              
220             my $result2 = Module::To::Test->get_data('D%');
221             is_deeply $result2, $d2, 'Got expected data for D%';
222              
223             done_testing();
224              
225             =head1 DESCRIPTION
226              
227             DBD::SimpleMock is a mock database driver for DBI that allows you to simulate database
228             interactions in your tests without needing a real database connection. It is particularly
229             useful for unit testing DBI-based applications.
230              
231             =head1 USAGE
232              
233             In your test, register your mock queries and their expected results using the `register_mocks` function from the SimpleMock module:
234              
235             use Test::More;
236             use SimpleMock qw(register_mocks);
237             register_mocks(
238             DBI => {
239             # optional meta settings
240             META => {
241             ...
242             },
243             # each query must have an 'sql' and at least one 'results' entry
244             # If you are using the `*_hashref` methods, you must also define 'cols'
245             QUERIES => [
246             {
247             sql => 'SELECT id, name, email FROM my_table WHERE name LIKE ?',
248             results => [
249             # data is an array ref of arrayrefs, each representing a row
250             { args => [ 'C%' ], data => $d1 },
251             { args => [ 'D%' ], data => $d2 },
252             # no args means it will match any query with no placeholders
253             { data => $d3 }, # this will match any query without placeholders
254             ],
255             # optional, if you are using the *_hashref methods
256             cols => [ 'id', 'name', 'email' ],
257             },
258             ],
259             }
260             });
261              
262             Meta tags are used to make aspects of the DBD to explicitly fail, or to allow unmocked queries to return empty results instead of throwing an exception.
263              
264             =head1 META SETTINGS
265              
266             These are the available flags you can set in the `META` section of your mocks registration:
267              
268             =over
269              
270             =item * `allow_unmocked_queries`: If set to 1, unmocked queries will return an empty result set and not throw an exception.
271              
272             =item * `connect_fail`: If set to 1, simulates a connection failure when connecting to the mock database.
273              
274             =item * `prepare_fail`: If set to 1, simulates a failure when preparing a statement.
275              
276             =item * `execute_fail`: If set to 1, simulates a failure when executing a statement.
277              
278             =back
279              
280             Note that if you set connect_fail, prepare_fail & execute_fail are moot at that point, so best practice is to only enable each as needed by the test.
281              
282             =cut