Learn R Programming

dittodb

dittodb is a package that makes testing against databases easy. When writing code that relies on interactions with databases, testing has been difficult without recreating test databases in your continuous integration (aka CI) environment, or resorting to using SQLite databases instead of the database engines you have in production. Both have their downsides: recreating database infrastructure is slow, error prone, and hard to iterate with. Using SQLite works well, right up until you use a feature (like a full outer join) or has quirks that might differ from your production database. dittodb solves this by recording database interactions, saving them as mocks, and then replaying them seamlessly during testing. This means that if you can get a query from your database, you can record the response and reliably reproduce that response in tests.

dittodb is heavily inspired by {httptest}, if you've used {httptest} before, you'll find many of the interactions similar.

A quick example

Say we have a database with some {nycflights} data in it and we are writing functions that query this data that we want to test.

For example, we have the simple function that retrieves one airline:

get_an_airline <- function(con) {
  return(dbGetQuery(con, "SELECT carrier, name FROM airlines LIMIT 1"))
}

But we want to make sure that this function returns what we expect. To do this, we first record the response we get from the production database:

{.tabset}

RMariaDB

start_db_capturing()

con <- DBI::dbConnect(
  RMariaDB::MariaDB(),
  dbname = "nycflights"
)

get_an_airline(con)
DBI::dbDisconnect(con)

stop_db_capturing()

RPostgres

start_db_capturing()

con <- DBI::dbConnect(
  RPostgres::Postgres(),
  dbname = "nycflights"
)

get_an_airline(con)
DBI::dbDisconnect(con)

stop_db_capturing()

RSQLite

start_db_capturing()

con <- DBI::dbConnect(RSQLite::SQLite(), "nycflights")

get_an_airline(con)
DBI::dbDisconnect(con)

stop_db_capturing()

{.tabset}

This will run the query from get_an_airline(), and save the response in a mock directory and file. Then, when we are testing, we can use the following:

RMariaDB

with_mock_db({
  con <- DBI::dbConnect(
    RMariaDB::MariaDB(),
    dbname = "nycflights"
  )
  
  test_that("We get one airline", {
    one_airline <- get_an_airline(con)
    expect_s3_class(one_airline, "data.frame")
    expect_equal(nrow(one_airline), 1)
    expect_equal(one_airline$carrier, "9E")
    expect_equal(one_airline$name, "Endeavor Air Inc.")
  })
})

RPostgres

with_mock_db({
  con <- DBI::dbConnect(
    RPostgres::Postgres(),
    dbname = "nycflights"
  )
  
  test_that("We get one airline", {
    one_airline <- get_an_airline(con)
    expect_s3_class(one_airline, "data.frame")
    expect_equal(nrow(one_airline), 1)
    expect_equal(one_airline$carrier, "9E")
    expect_equal(one_airline$name, "Endeavor Air Inc.")
  })
})

RSQLite

with_mock_db({
  con <- DBI::dbConnect(RSQLite::SQLite(), "nycflights")
  
  test_that("We get one airline", {
    one_airline <- get_an_airline(con)
    expect_s3_class(one_airline, "data.frame")
    expect_equal(nrow(one_airline), 1)
    expect_equal(one_airline$carrier, "9E")
    expect_equal(one_airline$name, "Endeavor Air Inc.")
  })
})

All without having to ever set a database up on Travis or GitHub Actions

Copy Link

Version

Install

install.packages('dittodb')

Monthly Downloads

613

Version

0.1.8

License

Apache License (>= 2.0)

Issues

Pull Requests

Stars

Forks

Maintainer

Jonathan Keane

Last Published

April 9th, 2024

Functions in dittodb (0.1.8)

mockPaths

Set an alternate directory for mock API fixtures
nycflights13_create_sqlite

Create an in-memory SQLite database for testing
nycflights_sqlite

An SQLite connection to a subset of nycflights13
use_dittodb

Use dittodb in your tests
set_dittodb_debug_level

Set dittodb's debug level
sanitize_table_id

Sanitize Table id
serialize_bit64

Make a data.frame with pre-serialized integer64 columns
redact_columns

Redact columns from a dataframe with the default redactors
with_mock_path

Run the DBI queries in an alternate mock directory
capture_requests

Capture and record database transactions and save them as mocks
db_path_sanitize

Switch illegal characters for legal ones
.dittodb_env

an environment for dittodb storing state
driver-specifc-mock-connections

Driver-specific mock classes
nycflights13_create_sql

Create a standardised database for testing
dittodb_debug_level

Get the dittodb debug level and evaluate if it is above a level
check_db_path

Check for dittodb environment path
expect_sql

Detect if a specific SQL statement is sent
clean_statement

Clean a statement string
check_for_pkg

Check if a package is installed
get_redactor

Get the current redactor
get_dbname

Get the dbname from a connection call
make_path

make a mock path
mock-db-methods

Methods for interacting with DB mocks instead of an actual database
mockdb

Run DBI queries against a mocked database
hash

Make a (short) hash from a string
get_type

Get the type of an SQL statement
hash_db_object

Extract a hash from a (result) object