diff --git a/DESCRIPTION b/DESCRIPTION index 1116b12b3..2bd079626 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -57,4 +57,4 @@ Config/testthat/parallel: true Config/testthat/start-first: watcher, parallel* Encoding: UTF-8 Roxygen: list(markdown = TRUE, r6 = FALSE) -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 diff --git a/R/mock-oo.R b/R/mock-oo.R index 7e01ea8f6..95fbd3929 100644 --- a/R/mock-oo.R +++ b/R/mock-oo.R @@ -1,9 +1,12 @@ #' Mock S3 and S4 methods #' +#' @description #' These functions allow you to temporarily override S3 and S4 methods that #' already exist. It works by using [registerS3method()]/[setMethod()] to #' temporarily replace the original definition. #' +#' Learn more about mocking in `vignette("mocking")`. +#' #' @param generic A string giving the name of the generic. #' @param signature A character vector giving the signature of the method. #' @param definition A function providing the method definition. @@ -67,12 +70,15 @@ local_mocked_s4_method <- function( #' Mock an R6 class #' +#' @description #' This function allows you to temporarily override an R6 class definition. #' It works by creating a subclass then using [local_mocked_bindings()] to #' temporarily replace the original definition. This means that it will not #' affect subclasses of the original class; please file an issue if you need #' this. #' +#' Learn more about mocking in `vignette("mocking")`. +#' #' @export #' @param class An R6 class definition. #' @param public,private A named list of public and private methods/data. diff --git a/R/mock2.R b/R/mock2.R index 6933d4142..d126c58e6 100644 --- a/R/mock2.R +++ b/R/mock2.R @@ -7,9 +7,7 @@ #' state (i.e. reading a value from a file or a website, or pretending a package #' is or isn't installed). #' -#' These functions represent a second attempt at bringing mocking to testthat, -#' incorporating what we've learned from the mockr, mockery, and mockthat -#' packages. +#' Learn more in `vignette("mocking")`. #' #' # Use #' diff --git a/man/expect_vector.Rd b/man/expect_vector.Rd index c67323e16..9e9b7463c 100644 --- a/man/expect_vector.Rd +++ b/man/expect_vector.Rd @@ -24,7 +24,7 @@ means that it used the vctrs of \code{ptype} (prototype) and \code{size}. See details in \url{https://vctrs.r-lib.org/articles/type-size.html} } \examples{ -\dontshow{if (requireNamespace("vctrs")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (requireNamespace("vctrs")) withAutoprint(\{ # examplesIf} expect_vector(1:10, ptype = integer(), size = 10) show_failure(expect_vector(1:10, ptype = integer(), size = 5)) show_failure(expect_vector(1:10, ptype = character(), size = 5)) diff --git a/man/local_mocked_bindings.Rd b/man/local_mocked_bindings.Rd index 3ade3f6a1..01645f389 100644 --- a/man/local_mocked_bindings.Rd +++ b/man/local_mocked_bindings.Rd @@ -31,9 +31,7 @@ during tests. This is helpful for testing functions that depend on external state (i.e. reading a value from a file or a website, or pretending a package is or isn't installed). -These functions represent a second attempt at bringing mocking to testthat, -incorporating what we've learned from the mockr, mockery, and mockthat -packages. +Learn more in \code{vignette("mocking")}. } \section{Use}{ There are four places that the function you are trying to mock might diff --git a/man/local_mocked_r6_class.Rd b/man/local_mocked_r6_class.Rd index 3e030cff6..e39f9e710 100644 --- a/man/local_mocked_r6_class.Rd +++ b/man/local_mocked_r6_class.Rd @@ -25,4 +25,6 @@ It works by creating a subclass then using \code{\link[=local_mocked_bindings]{l temporarily replace the original definition. This means that it will not affect subclasses of the original class; please file an issue if you need this. + +Learn more about mocking in \code{vignette("mocking")}. } diff --git a/man/local_mocked_s3_method.Rd b/man/local_mocked_s3_method.Rd index 8ab245b00..c309fa3b4 100644 --- a/man/local_mocked_s3_method.Rd +++ b/man/local_mocked_s3_method.Rd @@ -23,6 +23,8 @@ Only needed when wrapping in another local helper.} These functions allow you to temporarily override S3 and S4 methods that already exist. It works by using \code{\link[=registerS3method]{registerS3method()}}/\code{\link[=setMethod]{setMethod()}} to temporarily replace the original definition. + +Learn more about mocking in \code{vignette("mocking")}. } \examples{ x <- as.POSIXlt(Sys.time()) diff --git a/vignettes/challenging-tests.Rmd b/vignettes/challenging-tests.Rmd index dd3e7c517..f62e54cd9 100644 --- a/vignettes/challenging-tests.Rmd +++ b/vignettes/challenging-tests.Rmd @@ -134,6 +134,25 @@ test_that("user must respond y or n", { }) ``` +If you don't care about reproducing the output of `continue()` and just want to recreate its return value, you can use `mock_output_sequence()`. This creates a function that returns the input supplied to `mock_output_sequence()` in sequence: the first input at the first call, the second input at the second call, etc. The following code shows how it works and how you might use it to test `readline()`: + +```{r} +f <- mock_output_sequence(1, 12, 123) +f() +f() +f() +``` + +And + +```{r} +test_that("user must respond y or n", { + local_mocked_bindings(readline = mock_output_sequence("x", "y")) + expect_true(continue("This is dangerous")) +}) +``` + + If you were testing the behavior of some function that uses `continue()`, you might want to mock `continue()` instead of `readline()`. For example, the function below requires user confirmation before overwriting an existing file. In order to focus our tests on the behavior of just this function, we mock `continue()` to return either `TRUE` or `FALSE` without any user messaging. ```{r} diff --git a/vignettes/custom-expectation.Rmd b/vignettes/custom-expectation.Rmd index c6802a0de..c13711477 100644 --- a/vignettes/custom-expectation.Rmd +++ b/vignettes/custom-expectation.Rmd @@ -21,6 +21,47 @@ This vignette shows you how to write your own expectations. Custom expectations In this vignette, you'll learn about the three-part structure of expectations, how to test your custom expectations, see a few examples, and, if you're writing a lot of expectations, learn how to reduce repeated code. +## Do you need it? + +But before you read the rest of the vignette and dive into the full details of creating a 100% correct expectation, consider if you can get away with a simpler wrapper. If you're just customising an existing expectation by changing some defaults, you're fine: + +```{r} +expect_df <- function(tbl) { + expect_s3_class(tbl, "data.frame") +} +``` + +If you're combining multiple expectations, you can introduce a subtle problem. For example, take this expectation from tidytext: + +```{r} +# from tidytext +expect_nrow <- function(tbl, n) { + expect_s3_class(tbl, "data.frame") + expect_equal(nrow(tbl), n) +} +``` + +If we use it in a test you can see there's an issue: + +```{r} +#| error: true +test_that("success", { + expect_nrow(mtcars, 32) +}) +test_that("failure 1", { + expect_nrow(mtcars, 30) +}) +test_that("failure 2", { + expect_nrow(matrix(1:5), 2) +}) +``` + +Each of these tests contain a single expectation, but they report a total of two successes and failures. It would be confusing if testthat didn't report these numbers correctly. But as a helper in your package, it's probably not a big deal. + +You might also notice that these failures generate a backtrace whereas built-in expectations don't. Again, it's not a big deal because the backtrace is correct, it's just not needed. + +These are both minor issues, so if they don't bother you, you can save yourself some pain by not reading this vignette 😀. + ## Expectation basics An expectation has three main parts, as illustrated by `expect_length()`: