You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Index to tools to solving challenging problems, organised by the problem, not the technique used to solve it. Designed to short so you can skim the whole thing, spot the problem your facing, and then see the tools you can use to solve it.
23
+
This vignette is a quick reference guide for testing challenging functions. It's organised by the problem, rather than technique used to solve it, so you can quickly skim the whole vignette, spot the problem your facing, then learn more about useful tools for solving it.
24
24
25
25
## Options and environment variables
26
26
27
-
In this case can temporarily override with withr functions:
27
+
If your function depends on options or environment variables, first try refactoring the functions to make the [inputs explicit](https://design.tidyverse.org/inputs-explicit.html). If that's not possible, then you can use a function like `withr::local_options()` or `withr::local_envvar()` to temporarily change options and environment values within a test. Learn more in `vignette("test-fixtures")`.
28
28
29
-
* Temporarily change options with `withr::local_options()`.
30
-
* Temporarily change env vars with `withr::local_envvar()`.
29
+
## Random numbers
31
30
32
-
`vignette("test-fixtures")`
31
+
Random number generators generate different numbers each time you can them because they update a special `.Random.seed` variable. You can temporarily set this seed to a known value to make your random numbers reproducible with `withr::local_seed()`, making random numbers a special case of test fixtures.
33
32
34
-
## HTTP requests
33
+
```{r}
34
+
#| label: random-local-seed
35
+
36
+
dice <- function() {
37
+
sample(6, 1)
38
+
}
35
39
36
-
* If you need to test HTTP requests, we recommend using {vcr} or {httptest2}.
40
+
test_that("dice returns different numbers", {
41
+
withr::local_seed(1234)
37
42
38
-
## User interaction
43
+
expect_equal(dice(), 4)
44
+
expect_equal(dice(), 2)
45
+
expect_equal(dice(), 6)
46
+
})
47
+
```
48
+
49
+
Alternatively, you might want to mock your function that wraps a random number generator:
50
+
51
+
```{r}
52
+
#| label: random-mock
53
+
54
+
roll_three <- function() {
55
+
sum(dice(), dice(), dice())
56
+
}
57
+
58
+
test_that("three dice adds values of individual calls", {
If you're trying to test functions that rely on HTTP requests, we recommend using {vcr} or {httptest2}. These packages provide the ability to record and then later reply HTTP requests so that you can test without an active internet connection. If your package is going to CRAN, we highly recommend either using one of these packages or using `skip_on_cran()` for your internet facing tests. This ensures that your package won't break on CRAN just because the service you're using is temporarily down.
41
67
42
-
`vignette("mocking")`
68
+
## User interaction
43
69
44
-
For example, if your code uses `readline` to get feedback from the user, you can mock it. This is a good place to `mock_output_sequence()` if you want to simulate the user answering multiple questions.
70
+
If you're testing a function that relies on user feedback from `readline()` or `menu()` or similar, you can use mocking (`vignette("mocking")`) to temporarily make those functions return fixed values. For example, imagine that you have the following function that asks the user if they want to continue:
45
71
46
72
```{r}
47
-
readline <- NULL
73
+
#| label: continue
48
74
49
-
continue <- function() {
50
-
repeat{
51
-
val <- readline("Do you want to continue? (y/n) ")
52
-
if (val %in% c("y", "n")) {
53
-
return(val == "y")
54
-
}
55
-
cat("! You must enter y or n\n")
75
+
continue <- function(prompt) {
76
+
cat(prompt, "\n", sep = "")
77
+
78
+
repeat {
79
+
val <- readline("Do you want to continue? (y/n) ")
80
+
if (val %in% c("y", "n")) {
81
+
return(val == "y")
82
+
}
83
+
cat("! You must enter y or n\n")
56
84
}
57
85
}
58
86
87
+
readline <- NULL
88
+
```
89
+
90
+
You can test its behaviour by mocking `readline()` and using a snapshot test:
91
+
92
+
```{r}
93
+
#| label: mock-readline
94
+
59
95
test_that("user must respond y or n", {
60
96
mock_readline <- local({
61
97
i <- 0
@@ -69,35 +105,41 @@ test_that("user must respond y or n", {
69
105
})
70
106
71
107
local_mocked_bindings(readline = mock_readline)
72
-
expect_snapshot(val <- continue())
108
+
expect_snapshot(val <- continue("This is dangerous"))
73
109
expect_true(val)
74
110
})
75
111
```
76
112
77
-
## Random numbers
78
-
79
-
Random number generation is a special case of test fixures (`vignette("test-fixtures")`) on the value of the special `.Random.seed` variable which is updated whenever you generate a random number. You can temporarily change this seed and reproducibly generate "random" numbers with `withr::local_seed()`.
113
+
If you were testing the behaviour of some function that used `continue()`, you might choose to mock it directly:
80
114
81
115
```{r}
82
-
dice <- function() {
83
-
sample(6, 1)
116
+
#| label: mock-continue
117
+
118
+
save_file <- function(path, data) {
119
+
if (file.exists(path)) {
120
+
if (!continue("`path` already exists")) {
121
+
stop("Failed to continue")
122
+
}
123
+
}
124
+
writeLines(data, path)
84
125
}
85
126
86
-
test_that("dice returns different numbers", {
87
-
withr::local_seed(1234)
127
+
test_that("save_file() requires confirmation to overwrite file", {
Errors, warnings, and other user-facing text should be tested to ensure they're helpful and consistent. Obviously you can't test this 100% automatically, but you can ensure that such messaging is clearly shown in PRs, so another human can take a look. This is exactly the point of snapshot tests.
139
+
## User-facing text
98
140
99
-
Test with snapshot tests:`vignette("snapshotting")`.
141
+
Errors, warnings, and other user-facing text should be tested to ensure they're helpful and consistent. Obviously you can't test this 100% automatically, but you can ensure that such messaging is clearly shown in PRs, so another human can take a look. This is exactly the point of snapshot tests; learn more in`vignette("snapshotting")`.
100
142
101
143
## Testing interfaces
102
144
103
-
Sometimes you want to ensure multiple functions obey the same interface (e.g. they have the same arguments, or all error in the same way). You can use nested tests for this: see`vignette("nested-tests")`.
145
+
Sometimes you want to ensure multiple functions obey the same interface (e.g. they have the same arguments, or all error in the same way). In this case you can write a helper function that calls `test_that()` and then use this inside your other tests. This works because testthat (as of 3.3.0) supports nested tests, which you can learn more about in`vignette("nested-tests")`.
0 commit comments