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   27 use strict;
  4         5  
  4         138  
2 4     4   14 use warnings;
  4         4  
  4         853  
3              
4             our $VERSION = '0.03';
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.03";
15              
16             our $drh = undef; # holds driver handle once initialised
17              
18             sub driver {
19 6 100   6 0 836 return $drh if $drh;
20 4         8 my ($class, $attr) = @_;
21 4         9 $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         209 $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   24 use strict;
  4         7  
  4         495  
39              
40             sub connect {
41 10     10 0 660 my $drh = shift;
42 10 100       23 if (SimpleMock::Model::DBI::_get_dbi_meta('connect_fail')) {
43             # If connect_fail is set, we simulate a connection failure
44 2         14 $drh->set_err(1000, "Database unavailable");
45 2         8 return;
46             }
47 8 50       24 my $dbh = $drh->SUPER::connect(@_)
48             or return $drh->set_err(1000, "Database unavailable");
49 8         382 $dbh->STORE(Active => 1);
50 8         18 return $dbh;
51             }
52              
53 0     0   0 sub DESTROY { undef }
54             }
55              
56              
57             { package DBD::SimpleMock::db;
58 4     4   15 use strict;
  4         6  
  4         107  
59 4     4   17 use DBI;
  4         4  
  4         144  
60 4     4   11 use base 'DBD::_::db';
  4         5  
  4         1275  
61 4     4   35 use Carp qw(croak);
  4         12  
  4         1216  
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 650 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 4987 my ($dbh, $statement)= @_;
77              
78             # fail if prepare_fail META tag is set
79 18 100       46 if (SimpleMock::Model::DBI::_get_dbi_meta('prepare_fail')) {
80 1         38 return $dbh->set_err(1001, "Prepare failed");
81             }
82              
83 17         51 my ($outer) = DBI::_new_sth($dbh, {
84             'Statement' => $statement,
85             });
86              
87 17         493 return $outer;
88             }
89              
90             sub FETCH {
91 1     1   42 my ($dbh, $attrib) = @_;
92 1         8 return $dbh->SUPER::FETCH($attrib);
93             }
94              
95             sub STORE {
96 50     50   463 my ($dbh, $attrib, $value) = @_;
97 50 100       75 if ($attrib eq 'AutoCommit') {
98 9 100       38 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       16 $value = ($value) ? -901 : -900;
102             }
103 49         195 return $dbh->SUPER::STORE($attrib, $value);
104             }
105              
106 1     1 0 4 sub ping { 1 }
107              
108             sub disconnect {
109 1     1 0 48 shift->STORE(Active => 0);
110             }
111              
112             }
113              
114              
115             { package DBD::SimpleMock::st; # ====== STATEMENT ======
116             our $imp_data_size = 0;
117 4     4   19 use strict;
  4         4  
  4         1265  
118              
119             sub bind_param {
120 2     2 0 9 my ($sth, $param, $value, $attr) = @_;
121 2         5 $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 427 my ($sth, @arg) = @_;
130 16 100       25 if (SimpleMock::Model::DBI::_get_dbi_meta('execute_fail')) {
131 1         72 return $sth->set_err(1002, "Execute failed");
132             }
133 15         41 my $mock = SimpleMock::Model::DBI::_get_mock_for($sth->{Statement}, \@arg);
134 14         26 my $field_count = @{$mock->{data}->[0]};
  14         30  
135 14         63 $sth->STORE(NUM_OF_FIELDS => $field_count);
136 14         34 $sth->STORE(Active => 1);
137 14         50 $sth->{simplemock_data} = $mock->{data};
138 14 100       41 $sth->{NAME} = $mock->{cols} if ($mock->{cols});
139 14         82 1;
140             }
141              
142             sub fetchrow_arrayref {
143 28     28 0 528 my $sth = shift;
144              
145 28         27 my $data = shift @{$sth->{simplemock_data}};
  28         80  
146 28 100 66     89 if (!$data || !@$data) {
147 9         2416 $sth->finish; # no more data so finish
148 9         25 return;
149             }
150 19         124 return $sth->_set_fbav($data);
151             }
152              
153             *fetch = \&fetchrow_arrayref;
154              
155             sub FETCH {
156 1     1   20 my ($sth, $attrib) = @_;
157 1         33 return $sth->SUPER::FETCH($attrib);
158             }
159              
160             sub STORE {
161 28     28   39 my ($sth, $attrib, $value) = @_;
162 28         83 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