Skip to content

Commit

Permalink
feat: mock_output_sequence() similar to {mockery}'s multiple return…
Browse files Browse the repository at this point in the history
… values
  • Loading branch information
maelle committed Feb 21, 2025
1 parent 50a99ec commit b321b22
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 2 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,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.2.9000
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export(local_test_context)
export(local_test_directory)
export(make_expectation)
export(matches)
export(mock_output_sequence)
export(new_expectation)
export(not)
export(prints_text)
Expand Down
47 changes: 47 additions & 0 deletions R/mock2-helpers.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#' Mock a sequence of output from a function
#'
#' Specify multiple return values for mocking
#'
#' @param values vector of values to return in sequence.
#' @param recycle whether to recycle. If `TRUE`, once all values have been returned,
#' they will be returned again in sequence.
#'
#' @return A function that you can use within `local_mocked_bindings()` and
#' `with_mocked_bindings()`
#' @export
#'
#' @examples
#' # inside local_mocked_bindings()
#' readline <- NULL
#' local_mocked_bindings(readline = mock_output_sequence(c("3", "This is a note", "n")))
#' # for understanding
#' mocked_sequence <- mock_output_sequence(c("3", "This is a note", "n"))
#' mocked_sequence()
#' mocked_sequence()
#' mocked_sequence()
#' try(mocked_sequence())
#' recycled_mocked_sequence <- mock_output_sequence(
#' c("3", "This is a note", "n"),
#' recycle = TRUE
#' )
#' recycled_mocked_sequence()
#' recycled_mocked_sequence()
#' recycled_mocked_sequence()
#' recycled_mocked_sequence()
#' @family mocking
mock_output_sequence <- function(values, recycle = FALSE) {
force(values)
i <- 1
function(...) {
if (i > length(values) && !recycle) {
cli::cli_abort(c(
"Can't find value for {i}th iteration.",
i = "{.arg values} has only {length(values)} values.",
i = "You can set {.arg recycle} to {.code TRUE}."
))
}
value <- rep_len(values, length.out = i)[[i]]
i <<- i + 1
value
}
}
12 changes: 12 additions & 0 deletions R/mock2.R
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@
#' my_wrapper = function(...) "new_value"
#' )
#' ```
#'
#' ## Multiple return values / sequence of outputs
#'
#' To mock a function that returns different values in sequence,
#' for instance an API call whose status would be 502 then 200,
#' or an user intput to `readline()`, you can use [mock_output_sequence()]
#'
#' ```R
#' local_mocked_bindings(readline = mock_output_sequence(c("3", "This is a note", "n")))
#' ```
#'
#' @export
#' @param ... Name-value pairs providing new values (typically functions) to
#' temporarily replace the named bindings.
Expand All @@ -103,6 +114,7 @@
#' under active development (i.e. loaded with [pkgload::load_all()]).
#' We don't recommend using this to mock functions in other packages,
#' as you should not modify namespaces that you don't own.
#' @family mocking
local_mocked_bindings <- function(..., .package = NULL, .env = caller_env()) {
bindings <- list2(...)
check_bindings(bindings)
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.

15 changes: 15 additions & 0 deletions man/local_mocked_bindings.Rd

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

45 changes: 45 additions & 0 deletions man/mock_output_sequence.Rd

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

10 changes: 10 additions & 0 deletions tests/testthat/_snaps/mock2-helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# mock_output_sequence() works

Code
mocked_sequence()
Condition
Error in `mocked_sequence()`:
! Can't find value for 4th iteration.
i `values` has only 3 values.
i You can set `recycle` to `TRUE`.

19 changes: 19 additions & 0 deletions tests/testthat/test-mock2-helpers.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
test_that("mock_output_sequence() works", {
mocked_sequence <- mock_output_sequence(c("3", "This is a note", "n"))
expect_equal(mocked_sequence(), "3")
expect_equal(mocked_sequence(), "This is a note")
expect_equal(mocked_sequence(), "n")
expect_snapshot(mocked_sequence(), error = TRUE)
})

test_that("mock_output_sequence()'s recycling works", {
mocked_sequence <- mock_output_sequence(
c("3", "This is a note", "n"),
recycle = TRUE
)
expect_equal(mocked_sequence(), "3")
expect_equal(mocked_sequence(), "This is a note")
expect_equal(mocked_sequence(), "n")
expect_equal(mocked_sequence(), "3")
})

0 comments on commit b321b22

Please sign in to comment.