Skip to content

Commit b321b22

Browse files
committed
feat: mock_output_sequence() similar to {mockery}'s multiple return values
1 parent 50a99ec commit b321b22

File tree

9 files changed

+151
-2
lines changed

9 files changed

+151
-2
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,4 @@ Config/testthat/parallel: true
5656
Config/testthat/start-first: watcher, parallel*
5757
Encoding: UTF-8
5858
Roxygen: list(markdown = TRUE, r6 = FALSE)
59-
RoxygenNote: 7.3.2
59+
RoxygenNote: 7.3.2.9000

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export(local_test_context)
164164
export(local_test_directory)
165165
export(make_expectation)
166166
export(matches)
167+
export(mock_output_sequence)
167168
export(new_expectation)
168169
export(not)
169170
export(prints_text)

R/mock2-helpers.R

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#' Mock a sequence of output from a function
2+
#'
3+
#' Specify multiple return values for mocking
4+
#'
5+
#' @param values vector of values to return in sequence.
6+
#' @param recycle whether to recycle. If `TRUE`, once all values have been returned,
7+
#' they will be returned again in sequence.
8+
#'
9+
#' @return A function that you can use within `local_mocked_bindings()` and
10+
#' `with_mocked_bindings()`
11+
#' @export
12+
#'
13+
#' @examples
14+
#' # inside local_mocked_bindings()
15+
#' readline <- NULL
16+
#' local_mocked_bindings(readline = mock_output_sequence(c("3", "This is a note", "n")))
17+
#' # for understanding
18+
#' mocked_sequence <- mock_output_sequence(c("3", "This is a note", "n"))
19+
#' mocked_sequence()
20+
#' mocked_sequence()
21+
#' mocked_sequence()
22+
#' try(mocked_sequence())
23+
#' recycled_mocked_sequence <- mock_output_sequence(
24+
#' c("3", "This is a note", "n"),
25+
#' recycle = TRUE
26+
#' )
27+
#' recycled_mocked_sequence()
28+
#' recycled_mocked_sequence()
29+
#' recycled_mocked_sequence()
30+
#' recycled_mocked_sequence()
31+
#' @family mocking
32+
mock_output_sequence <- function(values, recycle = FALSE) {
33+
force(values)
34+
i <- 1
35+
function(...) {
36+
if (i > length(values) && !recycle) {
37+
cli::cli_abort(c(
38+
"Can't find value for {i}th iteration.",
39+
i = "{.arg values} has only {length(values)} values.",
40+
i = "You can set {.arg recycle} to {.code TRUE}."
41+
))
42+
}
43+
value <- rep_len(values, length.out = i)[[i]]
44+
i <<- i + 1
45+
value
46+
}
47+
}

R/mock2.R

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@
9292
#' my_wrapper = function(...) "new_value"
9393
#' )
9494
#' ```
95+
#'
96+
#' ## Multiple return values / sequence of outputs
97+
#'
98+
#' To mock a function that returns different values in sequence,
99+
#' for instance an API call whose status would be 502 then 200,
100+
#' or an user intput to `readline()`, you can use [mock_output_sequence()]
101+
#'
102+
#' ```R
103+
#' local_mocked_bindings(readline = mock_output_sequence(c("3", "This is a note", "n")))
104+
#' ```
105+
#'
95106
#' @export
96107
#' @param ... Name-value pairs providing new values (typically functions) to
97108
#' temporarily replace the named bindings.
@@ -103,6 +114,7 @@
103114
#' under active development (i.e. loaded with [pkgload::load_all()]).
104115
#' We don't recommend using this to mock functions in other packages,
105116
#' as you should not modify namespaces that you don't own.
117+
#' @family mocking
106118
local_mocked_bindings <- function(..., .package = NULL, .env = caller_env()) {
107119
bindings <- list2(...)
108120
check_bindings(bindings)

man/expect_vector.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/local_mocked_bindings.Rd

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/mock_output_sequence.Rd

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# mock_output_sequence() works
2+
3+
Code
4+
mocked_sequence()
5+
Condition
6+
Error in `mocked_sequence()`:
7+
! Can't find value for 4th iteration.
8+
i `values` has only 3 values.
9+
i You can set `recycle` to `TRUE`.
10+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
test_that("mock_output_sequence() works", {
2+
mocked_sequence <- mock_output_sequence(c("3", "This is a note", "n"))
3+
expect_equal(mocked_sequence(), "3")
4+
expect_equal(mocked_sequence(), "This is a note")
5+
expect_equal(mocked_sequence(), "n")
6+
expect_snapshot(mocked_sequence(), error = TRUE)
7+
})
8+
9+
test_that("mock_output_sequence()'s recycling works", {
10+
mocked_sequence <- mock_output_sequence(
11+
c("3", "This is a note", "n"),
12+
recycle = TRUE
13+
)
14+
expect_equal(mocked_sequence(), "3")
15+
expect_equal(mocked_sequence(), "This is a note")
16+
expect_equal(mocked_sequence(), "n")
17+
expect_equal(mocked_sequence(), "3")
18+
})
19+

0 commit comments

Comments
 (0)