NAME

DBIx::Fast::Test::Mock - a DBIx::Fast with canned answers, for unit tests

SYNOPSIS

use DBIx::Fast::Test::Mock;

my $mock = DBIx::Fast::Test::Mock->new(dialect => 'MariaDB');
$mock->on(qr/FROM users WHERE id = \?/ => [ { id => 7, name => 'ana' } ])
     ->on('SELECT COUNT(*) FROM orders'  => 3)
     ->on(qr/^INSERT INTO `orders`/      => { last_id => 501 })
     ->on(qr/^UPDATE/                    => sub ($sql, @binds) { { rows => 2 } })
     ->on(qr/^INSERT INTO `coupons`/     => { error => "Duplicate entry 'X'", err => 1062 });

my $db = $mock->db;                  # a real DBIx::Fast - pass it to the code under test
my $user = $db->hash('SELECT * FROM users WHERE id = ?', 7);   # { id => 7, name => 'ana' }

is($mock->called(qr/^UPDATE/), 1);
is_deeply($mock->last_query, { sql => '...', binds => [...] });

DESCRIPTION

Unit tests for code that uses DBIx::Fast, without a database server. The mock sits below DBIx::Fast (a private DBI driver), so every method runs its real code - SQL generation, identifier quoting, the dialect's upsert, JSON columns, timestamps, typed exceptions, txn - and only the server is replaced: each statement is recorded and answered from the rules given with "on". dialect selects the SQL DBIx::Fast generates (MariaDB, mysql, Pg or SQLite).

Keep the mock object alive while its db is in use.

METHODS

new

DBIx::Fast::Test::Mock->new(
    dialect => 'MariaDB',      # MariaDB (default), mysql, Pg, SQLite
    strict  => 0,              # 1: an unmatched statement is a DB error
    args    => { timestamps => {...}, after_write => sub {...} },
);

args are passed to "new" in DBIx::Fast (everything except the connection).

db

The DBIx::Fast instance.

on

$mock->on($match => $response);

$match is a regex, or an SQL string compared with whitespace collapsed. The first rule that matches answers. $response is:

a plain scalar - one row, one column (val, count)
[ {...}, ... ] - rows as hashes (columns in sorted key order)
[ [...], ... ] - rows as arrays (columns col1, col2...)
{ columns => [...], data => [...] } - rows with an explicit column order
{ rows => N, last_id => N } - a write: affected rows (default 1) and the id insert returns
{ error => $msg, err => $code, state => $sqlstate } - a database error, raised as the matching DBIx::Fast::X class (err => 1062 on MariaDB is a DBIx::Fast::X::Duplicate)
sub ($sql, @binds) { ... } - computed per call; returns any of the above

Unmatched statements: reads return no rows, writes affect one row (an INSERT gets an increasing last_id) - or, with strict, fail.

With the Pg dialect the first insert/insert_ignore into a table also runs a catalog lookup of its primary key (as against a real server; it is recorded like any statement). Unanswered, the insert takes the last_insert_id path; answer it to get INSERT ... RETURNING:

$mock->on(qr/pg_index/ => 'id')                  # the primary key column
     ->on(qr/^INSERT INTO "orders"/ => [ [501] ]); # RETURNING "id" -> 501

queries, sql, last_query

my @q = $mock->queries;      # ({ sql => ..., binds => [...] }, ...)
my @s = $mock->sql;          # just the statements
my $q = $mock->last_query;

Transactions appear as BEGIN, COMMIT and ROLLBACK.

called

my $n = $mock->called(qr/^DELETE/);

reset, clear_rules

Forget the recorded statements / the rules.

dialect, strict

Readers for the constructor arguments.

SEE ALSO

DBIx::Fast, DBIx::Fast::X

AUTHOR

SeHarrys

LICENSE

This is free software under the Artistic License 2.0.