Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions R/mock-oo.R
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 1 addition & 3 deletions R/mock2.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
#'
Expand Down
2 changes: 1 addition & 1 deletion man/expect_vector.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions man/local_mocked_bindings.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions man/local_mocked_r6_class.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions man/local_mocked_s3_method.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions vignettes/challenging-tests.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -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 each time it's called returns the next input supplied to `mock_output_sequence()`. 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}
Expand Down
29 changes: 29 additions & 0 deletions vignettes/custom-expectation.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,35 @@ 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 fully 100% correct expectation, consider if you can get away with a simpler wrapper. For example, take this expectation from tidytext:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not start with an example of a wrapper that is fine? For instance it could be

expect_df <- function(tbl) {
  expect_s3_class(tbl, "data.frame")
}

Or here is a real example: https://github.com/ropensci/aorsf/blob/2b7fa72bff5cd4e8b2984358e35d0938e50540b0/tests/testthat/helper-orsf.R#L259

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, thanks!


```{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 a subtle problem:

```{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 2 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 and you save yourself some pain by not reading this vignette 😀. You might also notice that these failures generate a backtrace whereas most failures don't. Again this is not a big deal, and the backtrace is correct, it's just not needed.

## Expectation basics

An expectation has three main parts, as illustrated by `expect_length()`:
Expand Down
Loading