library(individual)
library(mockery)

# Utility function for printing out C++ code
lang_output <- function(x, lang) {
  cat(c(sprintf("```%s", lang), x, "```"), sep = "\n")
}
cpp_output <- function(x) lang_output(x, "cpp")
print_cpp_file <- function(path) cpp_output(readLines(path))

Testing your model

So you've created a model, but you want to know if your processes are correct. This vignette walks you through how to mock the individual API and write unit tests to check your processes.

R processes

Let's test the rendering function from our “Modelling” vignette:

render_state_sizes <- function(api) {
  api$render('susceptible_counts', length(api$get_state(human, S)))
  api$render('infected_counts', length(api$get_state(human, I)))
  api$render('recovered_counts', length(api$get_state(human, R)))
}

Mockery is a lovely R package which allows you to stub and spy arbitrary objects and functions.

We could test that the correct counts are being rendered like so:

human <- mockery::mock()
S <- mockery::mock()
I <- mockery::mock()
R <- mockery::mock()

api <- list(
  get_state = mockery::mock( # create a function which
    seq(10), #returns 1:10 on the first call
    seq(5) + 10, #returns 11:15 on the second call, and
    seq(2) + 15 #returns 16:18 on the third call
  ),
  render = mockery::mock()
)

render_state_sizes(api)

mockery::expect_called(api$get_state, 3) #get_state is called three times
mockery::expect_args(api$get_state, 1, human, S) #the first time for the S state
mockery::expect_args(api$get_state, 2, human, I) #then for the I state
mockery::expect_args(api$get_state, 3, human, R) #then for the R state

mockery::expect_called(api$render, 3) #render is called three times
mockery::expect_args(api$render, 1, 'susceptible_counts', 10) #the first time for the S state
mockery::expect_args(api$render, 2, 'infected_counts', 5) #then for the I state
mockery::expect_args(api$render, 3, 'recovered_counts', 2) #then for the R state

C++ processes

Let's say you had a C++ version of the render function:

#include <individual.h>
#include <Rcpp.h>

//[[Rcpp::export]]
Rcpp::XPtr<process_t> create_render_process() {
    return Rcpp::XPtr<process_t>(
        new process_t([=] (ProcessAPI& api) {
              api.render(
                "susceptible_counts",
                api.get_state("human", "susceptible").size()
              );
              api.render(
                "infected_counts",
                api.get_state("human", "infected").size()
              );
              api.render(
                "recovered_counts",
                api.get_state("human", "recovered").size()
              );
        }),
        true
    );
}

The mockcpp package provides a mocking framework which lets you write analogous C++ unit tests:

#include <Rcpp.h>
#include <individual.h>
#include <testthat.h>
#include <mockcpp.h>
#include "code.h"

/*
 * Derive a mock api class from the ProcessAPI
 * See https://github.com/rollbear/trompeloeil
 */
class MockAPI : public ProcessAPI {
public:
    MockAPI() : ProcessAPI( // Initialise the ProcessAPI using some empty state
        Rcpp::XPtr<State>(static_cast<State *>(nullptr), false),
        Rcpp::XPtr<Scheduler<ProcessAPI>>(static_cast<Scheduler<ProcessAPI>*>(nullptr), false),
        Rcpp::List(),
        Rcpp::Environment()
    ) {};
    MAKE_CONST_MOCK2(get_state, const individual_index_t&(const std::string& individual, const std::string& state), override);
    MAKE_MOCK2(render, void(const std::string& label, double value), override);
};


context("State rendering") {
    test_that("state rendering returns the correct counts") {
          MockAPI api;
          auto population_size = 20;

          /*
           * Check the get_state calls
           */
          auto S = individual_index_t(
            population_size,
            std::vector<size_t>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
          );
          auto I = individual_index_t(
            population_size,
            std::vector<size_t>{10, 11, 12, 13, 14}
          );
          auto R = individual_index_t(
            population_size,
            std::vector<size_t>{15, 16}
          );
          REQUIRE_CALL(api, get_state("human", "susceptible")).RETURN(S);
          REQUIRE_CALL(api, get_state("human", "infected")).RETURN(I);
          REQUIRE_CALL(api, get_state("human", "recovered")).RETURN(R);

          /*
           * Check the render calls
           */
          REQUIRE_CALL(api, render("susceptible_counts", 10));
          REQUIRE_CALL(api, render("infected_counts", 5));
          REQUIRE_CALL(api, render("recovered_counts", 2));

          /*
           * Call the render process with our mocked api
           */
          auto renderer = create_render_process();
          (*renderer)(api);
    }
}

We can then build our package and run the test to ensure our mocking works:

temp_lib <- tempdir(check = FALSE)

# Install test package
install.packages(
  system.file('testcpp', package = 'individual', mustWork = TRUE),
  repos = NULL,
  type = 'source',
  lib = temp_lib
)

# load test package
library('testcpp', lib.loc = temp_lib)

# run the test
testthat::test_file(
  system.file(
    'testcpp/tests/testthat/test-cpp.R',
    package = 'individual',
    mustWork = TRUE
  )
)
#> ✔ |  OK F W S | Context
#> 
⠏ |   0       | C++
✔ |   1       | C++
#> 
#> ══ Results ═════════════════════════════════════════════════════════════════════
#> OK:       1
#> Failed:   0
#> Warnings: 0
#> Skipped:  0

And there you go, both R and C++ processes can be unit tested by mocking the simulation API.