Skip to content

Commit aeaa1c7

Browse files
committed
Deprecate old mocking functions
1 parent fe38519 commit aeaa1c7

File tree

10 files changed

+271
-528
lines changed

10 files changed

+271
-528
lines changed

NEWS.md

Lines changed: 234 additions & 232 deletions
Large diffs are not rendered by default.

R/mock.R

Lines changed: 6 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
#' Mock functions in a package.
22
#'
33
#' @description
4-
#' `r lifecycle::badge("superseded")`
5-
#'
6-
#' `with_mock()` and `local_mock()` are superseded in favour of
4+
#' `with_mock()` and `local_mock()` are deprecated in favour of
75
#' [with_mocked_bindings()] and [local_mocked_bindings()].
86
#'
9-
#' These works by using some C code to temporarily modify the mocked function
10-
#' _in place_. This is abusive of R's internals, which makes it dangerous, and
11-
#' no longer recommended.
7+
#' These functions worked by using some C code to temporarily modify the mocked
8+
#' function _in place_. This was an abuse of R's internals and it is no longer
9+
#' permitted.
1210
#'
1311
#' @section 3rd edition:
1412
#' `r lifecycle::badge("deprecated")`
@@ -26,116 +24,11 @@
2624
#' @return The result of the last unnamed parameter
2725
#' @export
2826
with_mock <- function(..., .env = topenv()) {
29-
edition_deprecate(3, "with_mock()", "Please use with_mocked_bindings() instead")
30-
31-
dots <- eval(substitute(alist(...)))
32-
mock_qual_names <- names(dots)
33-
34-
if (all(mock_qual_names == "")) {
35-
warning(
36-
"Not mocking anything. Please use named parameters to specify the functions you want to mock.",
37-
call. = FALSE
38-
)
39-
code_pos <- rep(TRUE, length(dots))
40-
} else {
41-
code_pos <- (mock_qual_names == "")
42-
}
43-
code <- dots[code_pos]
44-
45-
mock_funs <- lapply(dots[!code_pos], eval, parent.frame())
46-
mocks <- extract_mocks(mock_funs, .env = .env)
47-
48-
on.exit(lapply(mocks, reset_mock), add = TRUE)
49-
lapply(mocks, set_mock)
50-
51-
# Evaluate the code
52-
if (length(code) > 0) {
53-
for (expression in code[-length(code)]) {
54-
eval(expression, parent.frame())
55-
}
56-
# Isolate last item for visibility
57-
eval(code[[length(code)]], parent.frame())
58-
}
27+
lifecycle::deprecate_stop("3.3.0", "with_mock()", "with_mocked_bindings()")
5928
}
6029

6130
#' @export
6231
#' @rdname with_mock
6332
local_mock <- function(..., .env = topenv(), .local_envir = parent.frame()) {
64-
edition_deprecate(3, "local_mock()", "Please use local_mocked_bindings() instead")
65-
66-
mocks <- extract_mocks(list(...), .env = .env)
67-
on_exit <- bquote(
68-
on.exit(lapply(.(mocks), .(reset_mock)), add = TRUE),
69-
)
70-
71-
lapply(mocks, set_mock)
72-
eval_bare(on_exit, .local_envir)
73-
invisible()
74-
}
75-
76-
pkg_rx <- ".*[^:]"
77-
colons_rx <- "::(?:[:]?)"
78-
name_rx <- ".*"
79-
pkg_and_name_rx <- sprintf("^(?:(%s)%s)?(%s)$", pkg_rx, colons_rx, name_rx)
80-
81-
extract_mocks <- function(funs, .env) {
82-
if (is.environment(.env)) {
83-
.env <- environmentName(.env)
84-
}
85-
mock_qual_names <- names(funs)
86-
87-
lapply(
88-
stats::setNames(nm = mock_qual_names),
89-
function(qual_name) {
90-
pkg_name <- gsub(pkg_and_name_rx, "\\1", qual_name)
91-
92-
if (is_base_pkg(pkg_name)) {
93-
stop(
94-
"Can't mock functions in base packages (", pkg_name, ")",
95-
call. = FALSE
96-
)
97-
}
98-
99-
name <- gsub(pkg_and_name_rx, "\\2", qual_name)
100-
101-
if (pkg_name == "") {
102-
pkg_name <- .env
103-
}
104-
105-
env <- asNamespace(pkg_name)
106-
107-
if (!exists(name, envir = env, mode = "function")) {
108-
stop("Function ", name, " not found in environment ",
109-
environmentName(env), ".",
110-
call. = FALSE
111-
)
112-
}
113-
mock(name = name, env = env, new = funs[[qual_name]])
114-
}
115-
)
116-
}
117-
118-
mock <- function(name, env, new) {
119-
target_value <- get(name, envir = env, mode = "function")
120-
structure(
121-
list(
122-
env = env,
123-
name = as.name(name),
124-
orig_value = .Call(duplicate_, target_value), target_value = target_value,
125-
new_value = new
126-
),
127-
class = "mock"
128-
)
129-
}
130-
131-
set_mock <- function(mock) {
132-
.Call(reassign_function, mock$name, mock$env, mock$target_value, mock$new_value)
133-
}
134-
135-
reset_mock <- function(mock) {
136-
.Call(reassign_function, mock$name, mock$env, mock$target_value, mock$orig_value)
137-
}
138-
139-
is_base_pkg <- function(x) {
140-
x %in% rownames(utils::installed.packages(priority = "base"))
33+
lifecycle::deprecate_stop("3.3.0", "local_mock()", "local_mocked_bindings()")
14134
}

src/init.c

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@
44
#include <R_ext/Rdynload.h>
55

66
/* .Call calls */
7-
extern SEXP duplicate_(SEXP);
8-
extern SEXP reassign_function(SEXP, SEXP, SEXP, SEXP);
97
extern SEXP run_testthat_tests(SEXP);
108

119
static const R_CallMethodDef CallEntries[] = {
12-
{"duplicate_", (DL_FUNC) &duplicate_, 1},
13-
{"reassign_function", (DL_FUNC) &reassign_function, 4},
1410
{"run_testthat_tests", (DL_FUNC) &run_testthat_tests, 1},
1511
{NULL, NULL, 0}
1612
};

src/reassign.c

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
R4.4
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# variants save different values
2+
3+
Code
4+
r_version()
5+
Output
6+
[1] "R4.4"
7+

tests/testthat/_snaps/mock.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# now defunct
2+
3+
Code
4+
local_mock()
5+
Condition
6+
Error:
7+
! `local_mock()` was deprecated in testthat 3.3.0 and is now defunct.
8+
i Please use `local_mocked_bindings()` instead.
9+
Code
10+
with_mock(is_testing = function() FALSE)
11+
Condition
12+
Error:
13+
! `with_mock()` was deprecated in testthat 3.3.0 and is now defunct.
14+
i Please use `with_mocked_bindings()` instead.
15+

tests/testthat/test-examples.R

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
test_that("test_examples works with installed packages", {
2-
local_edition(2)
3-
4-
local_mock(test_rd = identity)
2+
local_mocked_bindings(test_rd = identity)
53
expect_true(length(test_examples()) > 1)
64
})
75

tests/testthat/test-mock.R

Lines changed: 5 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,150 +1,6 @@
1-
test_that("deprecated in 3rd edition", {
2-
expect_warning(local_mock(), "deprecated")
3-
expect_warning(with_mock(is_testing = function() FALSE), "deprecated")
4-
})
5-
6-
test_that("can change value of internal function", {
7-
local_edition(2)
8-
9-
with_mock(
10-
test_mock_internal2 = function() 5,
11-
expect_equal(test_mock_internal(), 5)
12-
)
13-
14-
# and value is restored on error
15-
expect_error(
16-
with_mock(
17-
test_mock_internal2 = function() 5,
18-
stop("!")
19-
)
20-
)
21-
expect_equal(test_mock_internal(), "y")
22-
})
23-
24-
25-
test_that("mocks can access local variables", {
26-
local_edition(2)
27-
x <- 5
28-
29-
with_mock(
30-
test_mock_internal2 = function() x,
31-
expect_equal(test_mock_internal(), 5)
32-
)
33-
})
34-
35-
test_that("non-empty mock with return value", {
36-
local_edition(2)
37-
expect_true(with_mock(
38-
compare = function(x, y, ...) list(equal = TRUE, message = "TRUE"),
39-
TRUE
40-
))
41-
})
42-
43-
test_that("nested mock", {
44-
local_edition(2)
45-
with_mock(
46-
all.equal = function(x, y, ...) TRUE,
47-
{
48-
with_mock(
49-
expect_warning = expect_error,
50-
{
51-
expect_warning(stopifnot(!compare(3, "a")$equal))
52-
}
53-
)
54-
},
55-
.env = asNamespace("base")
56-
)
57-
expect_false(isTRUE(all.equal(3, 5)))
58-
expect_warning(warning("test"))
59-
})
60-
61-
test_that("can't mock non-existing", {
62-
local_edition(2)
63-
expect_error(with_mock(..bogus.. = identity, TRUE), "Function [.][.]bogus[.][.] not found in environment testthat")
64-
})
65-
66-
test_that("can't mock non-function", {
67-
local_edition(2)
68-
expect_error(with_mock(pkg_and_name_rx = FALSE, TRUE), "Function pkg_and_name_rx not found in environment testthat")
69-
})
70-
71-
test_that("empty or no-op mock", {
72-
local_edition(2)
73-
expect_warning(
74-
expect_null(with_mock()),
75-
"Not mocking anything. Please use named parameters to specify the functions you want to mock.",
76-
fixed = TRUE
77-
)
78-
expect_warning(
79-
expect_true(with_mock(TRUE)),
80-
"Not mocking anything. Please use named parameters to specify the functions you want to mock.",
81-
fixed = TRUE
82-
)
83-
})
84-
85-
test_that("visibility", {
86-
local_edition(2)
87-
expect_warning(expect_false(withVisible(with_mock())$visible))
88-
expect_true(withVisible(with_mock(compare = function() {}, TRUE))$visible)
89-
expect_false(withVisible(with_mock(compare = function() {}, invisible(5)))$visible)
90-
})
91-
92-
test_that("multiple return values", {
93-
local_edition(2)
94-
expect_true(with_mock(FALSE, TRUE, compare = function() {}))
95-
expect_equal(with_mock(3, compare = function() {}, 5), 5)
96-
})
97-
98-
test_that("can access variables defined in function", {
99-
local_edition(2)
100-
x <- 5
101-
expect_equal(with_mock(x, compare = function() {}), 5)
102-
})
103-
104-
test_that("can mock if package is not loaded", {
105-
local_edition(2)
106-
if ("package:curl" %in% search()) {
107-
skip("curl is loaded")
108-
}
109-
skip_if_not_installed("curl")
110-
with_mock(`curl::curl` = identity, expect_identical(curl::curl, identity))
111-
})
112-
113-
test_that("changes to variables are preserved between calls and visible outside", {
114-
local_edition(2)
115-
x <- 1
116-
with_mock(
117-
show_menu = function() {},
118-
x <- 3,
119-
expect_equal(x, 3)
120-
)
121-
expect_equal(x, 3)
122-
})
123-
124-
test_that("mock extraction", {
125-
local_edition(2)
126-
expect_identical(
127-
extract_mocks(list(compare = compare), .env = asNamespace("testthat"))$compare$name,
128-
as.name("compare")
129-
)
130-
expect_error(
131-
extract_mocks(list(..bogus.. = identity), "testthat"),
132-
"Function [.][.]bogus[.][.] not found in environment testthat"
133-
)
134-
expect_equal(
135-
length(extract_mocks(list(not = identity, show_menu = identity), "testthat")),
136-
2
137-
)
138-
})
139-
# local_mock --------------------------------------------------------------
140-
141-
test_that("local_mock operates locally", {
142-
local_edition(2)
143-
f <- function() {
144-
local_mock(compare = function(x, y) FALSE)
145-
compare(1, 1)
146-
}
147-
148-
expect_false(f())
149-
expect_equal(compare(1, 1), no_difference())
1+
test_that("now defunct", {
2+
expect_snapshot(error = TRUE, {
3+
local_mock()
4+
with_mock(is_testing = function() FALSE)
5+
})
1506
})

tests/testthat/test-reporter-debug.R

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
test_that("produces consistent output", {
22
withr::local_options(testthat.edition_ignore = TRUE)
33
local_edition(2)
4-
local_mock(
4+
local_mocked_bindings(
55
show_menu = function(choices, title = NULL) {
66
cat(paste0(format(seq_along(choices)), ": ", choices, sep = "\n"), "\n", sep = "")
77
0L
@@ -22,7 +22,7 @@ get_frame_from_debug_reporter <- function(choice, fun, envir = parent.frame()) {
2222
force(choice)
2323
test_debug_reporter_parent_frame <- NULL
2424

25-
with_mock(
25+
with_mocked_bindings(
2626
show_menu = function(choices, title = NULL) {
2727
# if (choice > 0) print(choices)
2828
my_choice <- choice
@@ -178,4 +178,3 @@ test_that("browser() is called for the correct frame for skips", {
178178
expect_equal(get_vars_from_debug_reporter(3, fun_3), "g")
179179
expect_equal(get_vars_from_debug_reporter(4, fun_3), character())
180180
})
181-

0 commit comments

Comments
 (0)