10  Test suite


testthat Workflow:

  • use_testthat(): setup testing infrastructure in your app-package

    • Include edition (i.e., use_testthat(3))
  • use_test(): creates new test files (with test- prefix)

    • Each file under R/ should a corresponding test- file
  • test_active_file(): runs tests in the current open test file

  • test_coverage_active_file(): test coverage for the current open test file

Behavior-driven development functions:

  • describe(): provides context (user specification or feature) for tests and test code

  • it(): used to test functional requirement (i.e., expectation functions).


Testing shiny applications poses some unique challenges. Shiny functions are written in the context of its reactive model,1 so some standard testing techniques and methods for regular R packages don’t directly apply. This chapter covers setting up the testthat’s infrastructure for unit tests. I’ll also introduce some keyboard shortcuts for commonly used functions while developing tests.

I’ve created the shinypak R package In an effort to make each section accessible and easy to follow:

Install shinypak using pak (or remotes):

# install.packages('pak')
pak::pak('mjfrigaard/shinypak')

Review the chapters in each section:

library(shinypak)
list_apps(regex = 'tests')
## # A tibble: 6 × 2
##   branch               last_updated       
##   <chr>                <dttm>             
## 1 11_tests-specs       2024-07-01 22:42:51
## 2 12.1_tests-fixtures  2024-07-01 22:44:45
## 3 12.2_tests-helpers   2024-07-01 22:48:30
## 4 12.3_tests-snapshots 2024-07-02 08:21:14
## 5 13_tests-modules     2024-07-02 13:43:56
## 6 14_tests-system      2024-07-02 14:20:12

Launch an app:

launch(app = "11_tests-specs")

10.1 testthat framework

testthat is the standard package for testing in R packages and one of the most widely used and supported packages on CRAN. Its widespread adoption is likely due to its ability to simplify the setup, creation, and execution of unit tests.

In our app-packages, we’ll use testthat unit tests to ensure the underlying logic (i.e., non-reactive utility functions) behaves correctly. Combining Shiny’s testServer() function and the shinytest2 package with testthat provides a comprehensive testing suite for our app-package.

10.2 Setting up testthat tests

The testthat package has been around for over a decade and thus has undergone various changes that require us to specify the edition we intend to use (currently, it’s the third):2

usethis::use_testthat(3)

Setting up your testing infrastructure with use_testthat() does the following (3 is the edition):

  1. In the DESCRIPTION file, testthat (>= 3.0.0) is listed under Suggests

  2. Config/testthat/edition: 3 is also listed in the DESCRIPTION to specify the testthat edition

  3. A new tests/ folder is created, with a testthat/ subfolder

  4. The tests/testthat/testthat.R file is created

We now have a tests/ folder to store our testthat tests.

tests/
  ├── testthat/
  └── testthat.R

2 directories, 1 file
1
Referred to as the ‘test runner,’ because it runs all our tests (do not edit this file).

10.3 Creating unit tests

The standard workflow for writing testthat unit tests consists of the following:

New tests are created with usethis::use_test():

usethis::use_test("scatter_plot") 
  • testthat recommends having a corresponding test file in tests/testthat/ (with the test- prefix) for the files in R/.

10.3.1 test- files

Test files: the IDE will automatically create and open the new test file:

✔ Writing 'tests/testthat/test-scatter_plot.R'
• Modify 'tests/testthat/test-scatter_plot.R'

10.3.2 test_that() tests

Each new test file contains a boilerplate test_that() test:

test_that(desc = "multiplication works", code = {
 
})
1
desc is the test context (supplied in "quotes"), and code is the test code (supplied in {curly brackets}).

10.3.3 expect_ations

Expectation typically have two parts: an observed object, and an expected object:

expect_equal(
  object = 2 * 2,
  expected = 4
  ) 
1
A testthat expectation function
2
The output or behavior being tested
3
A predefined output or behavior

The observed object is an artifact of some code we’ve written, and it’s being compared against an expected result.

10.3.4 BDD test functions

testthat also has two behavior-driven development (BDD) functions for performing tests: describe() and it().

Use describe() to verify that you implement the right things and use [it()] to ensure you do the things right.” - testthat documentation

describe("Description of feature or specification",
      code = { 
    it("Functionality under test",
        code = {
            expect_equal( 
            object = 2 * 2, 
            expected = 4 
            )
        })
    })
1
describe() the feature or specification
2
Capture it() in a test
3
Write expectations

We’ll cover BDD more in the next chapter, but for now just know that each call to it() behaves like test_that().

10.3.5 Running tests

Another devtools habit to adopt is regularly writing and running tests. If you’re using Posit Workbench and have devtools installed, you can test your app-package using the Build pane or the keyboard shortcut: Ctrl/Cmd + Shift + T

Run all tests

10.3.6 Keyboard shortcuts

R Packages, 2ed also suggests binding test_active_file() and test_coverage_active_file() to keyboard shortcuts. I highly recommend using a shortcut while developing tests because it will improve your ability to iterate quickly.

devtools function

test()

 

Keyboard shortcut

Ctrl/Cmd + Shift + T

test_active_file()

 

Ctrl/Cmd + T

test_coverage_active_file()

 

Ctrl/Cmd + Shift + R

When the test is run, we’ll see feedback on whether it passes or fails (and occasionally some encouragement):

test_that("multiplication works", { 
  expect_equal( 
    object = 2 * 2, 
    expected = 4 
    ) 
})
## Test passed 😸

Recap

RECAP  


testthat setup

  • use_testthat(): sets up testing infrastructure in your app-package

Test files

  • use_test(): creates new test files (with test- prefix). The test file names should generally match the file names be belowR/.

BDD test functions

  • describe(): Feature descriptions and any relevant background information

  • it(): Scenarios and test code with expectations (Then statement = functional requirement).

Running tests

  • test_active_file(): runs tests in the current open test file

  • test_coverage_active_file(): test coverage for the current open test file

Please open an issue on GitHub


  1. The ‘Reactivity - An overview’ article gives an excellent description (and mental module) of reactive programming.↩︎

  2. Read more about changes to the third edition to testthat in R Packages, 2ed↩︎