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   24 use strict;
  4         5  
  4         127  
2 4     4   12 use warnings;
  4         4  
  4         840  
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 871 return $drh if $drh;
20 4         8 my ($class, $attr) = @_;
21 4         7 $class .= "::dr";
22 4         21 ($drh) = DBI::_new_drh($class, {
23             'Name' => 'SimpleMock',
24             'Version' => $VERSION,
25             'Attribution' => 'Mock DBD for tests',
26             });
27 4         164 $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   19 use strict;
  4         4  
  4         467  
39              
40             sub connect {
41 10     10 0 657 my $drh = shift;
42 10 100       25 if (SimpleMock::Model::DBI::_get_dbi_meta('connect_fail')) {
43             # If connect_fail is set, we simulate a connection failure
44 2         13 $drh->set_err(1000, "Database unavailable");
45 2         9 return;
46             }
47 8 50       23 my $dbh = $drh->SUPER::connect(@_)
48             or return $drh->set_err(1000, "Database unavailable");
49 8         338 $dbh->STORE(Active => 1);
50 8         15 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         4  
  4         77  
59 4     4   9 use DBI;
  4         4  
  4         172  
60 4     4   19 use base 'DBD::_::db';
  4         3  
  4         1309  
61 4     4   22 use Carp qw(croak);
  4         5  
  4         1234  
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 595 my ($dbh, $type) = @_;
68              
69 2 100       7 if ($type == 29) {
70 1         4 return '"';
71             }
72 1         4 return;
73             }
74              
75             sub prepare {
76 18     18 0 4377 my ($dbh, $statement)= @_;
77              
78             # fail if prepare_fail META tag is set
79 18 100       44 if (SimpleMock::Model::DBI::_get_dbi_meta('prepare_fail')) {
80 1         36 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         448 return $outer;
88             }
89              
90             sub FETCH {
91 1     1   41 my ($dbh, $attrib) = @_;
92 1         8 return $dbh->SUPER::FETCH($attrib);
93             }
94              
95             sub STORE {
96 50     50   458 my ($dbh, $attrib, $value) = @_;
97 50 100       92 if ($attrib eq 'AutoCommit') {
98 9 100       47 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       19 $value = ($value) ? -901 : -900;
102             }
103 49         186 return $dbh->SUPER::STORE($attrib, $value);
104             }
105              
106 1     1 0 3 sub ping { 1 }
107              
108             sub disconnect {
109 1     1 0 33 shift->STORE(Active => 0);
110             }
111              
112             }
113              
114              
115             { package DBD::SimpleMock::st; # ====== STATEMENT ======
116             our $imp_data_size = 0;
117 4     4   18 use strict;
  4         5  
  4         1251  
118              
119             sub bind_param {
120 2     2 0 9 my ($sth, $param, $value, $attr) = @_;
121 2         4 $sth->{ParamValues}{$param} = $value;
122 2 100       5 if (defined $attr) {
123             # attr (SQL type hints) accepted but not used by mock driver
124             }
125 2         7 return 1;
126             }
127              
128             sub execute {
129 16     16 0 381 my ($sth, @arg) = @_;
130 16 100       25 if (SimpleMock::Model::DBI::_get_dbi_meta('execute_fail')) {
131 1         80 return $sth->set_err(1002, "Execute failed");
132             }
133 15         39 my $mock = SimpleMock::Model::DBI::_get_mock_for($sth->{Statement}, \@arg);
134 14         23 my $field_count = @{$mock->{data}->[0]};
  14         26  
135 14         62 $sth->STORE(NUM_OF_FIELDS => $field_count);
136 14         26 $sth->STORE(Active => 1);
137 14         19 $sth->{simplemock_data} = $mock->{data};
138 14 100       42 $sth->{NAME} = $mock->{cols} if ($mock->{cols});
139 14         44 1;
140             }
141              
142             sub fetchrow_arrayref {
143 28     28 0 585 my $sth = shift;
144              
145 28         25 my $data = shift @{$sth->{simplemock_data}};
  28         36  
146 28 100 66     121 if (!$data || !@$data) {
147 9         39 $sth->finish; # no more data so finish
148 9         17 return;
149             }
150 19         109 return $sth->_set_fbav($data);
151             }
152              
153             *fetch = \&fetchrow_arrayref;
154              
155             sub FETCH {
156 1     1   16 my ($sth, $attrib) = @_;
157 1         54 return $sth->SUPER::FETCH($attrib);
158             }
159              
160             sub STORE {
161 28     28   39 my ($sth, $attrib, $value) = @_;
162 28         81 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             =item * `connect_fail`: If set to 1, simulates a connection failure when connecting to the mock database.
272             =item * `prepare_fail`: If set to 1, simulates a failure when preparing a statement.
273             =item * `execute_fail`: If set to 1, simulates a failure when executing a statement.
274              
275             =back
276              
277             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.
278              
279             =cut