mockr/0000755000176200001440000000000013101111256011354 5ustar liggesusersmockr/tests/0000755000176200001440000000000013027325454012534 5ustar liggesusersmockr/tests/testthat.R0000644000176200001440000000006613027325454014521 0ustar liggesuserslibrary(testthat) library(mockr) test_check("mockr") mockr/tests/testthat/0000755000176200001440000000000013101111256014356 5ustar liggesusersmockr/tests/testthat/test-mock.R0000644000176200001440000000650213073253663016433 0ustar liggesuserscontext("Mock") test_that("direct mocking", { with_mock( mockee = function() 42, expect_equal(mockee(), 42) ) }) test_that("infinite depth", { call_mockee <- function() mockee() with_mock( mockee = function() 42, expect_equal(call_mockee(), 42) ) }) test_that("infinite depth in package", { with_mock( mockee = function() 42, expect_equal(mockee3(), 42) ) }) test_that("mocked function is restored on error", { expect_error( with_mock( mockee = function(x, y, ...) list(equal = TRUE, message = "TRUE"), stop("Simulated error") ), "Simulated error" ) }) test_that("non-empty mock with return value", { expect_true(with_mock( mockee = function(x, y, ...) list(equal = TRUE, message = "TRUE"), TRUE )) }) test_that("nested mock", { with_mock( mockee = function() mockee2(), { with_mock( mockee2 = function() 42, { expect_equal(mockee(), 42) } ) } ) expect_error(mockee()) expect_error(mockee2()) }) test_that("qualified mock names warn", { expect_warning(with_mock("mockr::mockee" = function() 42, mockee()), "cannot mock functions defined in other packages") }) test_that("can't mock non-existing", { expect_error(with_mock(..bogus.. = identity, TRUE), "[.][.]bogus[.][.] not found in environment mockr") }) test_that("can't mock non-function", { expect_error(with_mock(some_symbol = FALSE, TRUE), "some_symbol is not a function in environment mockr") }) test_that("empty or no-op mock", { expect_warning(expect_null(with_mock()), "Not (?:mocking|evaluating) anything", all = TRUE) expect_warning(expect_true(with_mock(TRUE)), "Not mocking anything") expect_warning(expect_null(with_mock(mockee = function() {})), "Not evaluating anything") expect_warning(expect_false(withVisible(with_mock(invisible(5)))$visible), "Not mocking anything") }) test_that("multi-mock", { expect_equal( with_mock( mockee = function() 1, mockee2 = function() 2, mockee() ), 1 ) expect_equal( with_mock( mockee = function() 1, mockee2 = function() 2, mockee2() ), 2 ) expect_equal( with_mock( mockee = function() 1, mockee2 = function() 2, mockee3() ), 1 ) }) test_that("multiple return values", { expect_true(with_mock(FALSE, TRUE, mockee = identity)) expect_equal(with_mock(3, mockee = identity, 5), 5) }) test_that("can access variables defined in function", { x <- 5 expect_equal(with_mock(x, mockee = identity), 5) }) test_that("changes to variables are preserved between calls and visible outside", { x <- 1 with_mock( mockee = identity, x <- 3, expect_equal(x, 3) ) expect_equal(x, 3) }) test_that("mocks can access local variables", { value <- TRUE with_mock( expect_true(mockee()), mockee = function() {value} ) }) test_that("mocks can update local variables", { value <- TRUE with_mock( expect_false(mockee()), mockee = function() { value <<- FALSE; value } ) expect_false(value) }) test_that("mocks are overridden by local functons", { mockee <- function() stop("Still not mocking") with_mock( expect_error(mockee(), "Still not mocking"), mockee = function() TRUE ) }) mockr/NAMESPACE0000644000176200001440000000010013027325454012600 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(with_mock) mockr/NEWS.md0000644000176200001440000000050613100650073012457 0ustar liggesusers# mockr 0.1 (2017-04-28) Initial CRAN release. - `with_mock()` modeled closely after `testthat::with_mock()`, can only mock in the package under test but avoids fiddling with R's internals. - The `.env` argument now can be a character, but using this argument may lead to different results than `testthat::with_mock()`. mockr/R/0000755000176200001440000000000013073253663011576 5ustar liggesusersmockr/R/utils.R0000644000176200001440000000060513073253663013062 0ustar liggesusersvlapply <- function(X, FUN, ..., USE.NAMES = TRUE) { vapply(X = X, FUN = FUN, FUN.VALUE = logical(1L), ..., USE.NAMES = USE.NAMES) } stopc <- function(...) { stop(..., call. = FALSE, domain = NA) } warningc <- function(...) { warning(..., call. = FALSE, domain = NA) } names2 <- function(x) { names(x) %||% rep("", length(x)) } "%||%" <- function(x, y) if(is.null(x)) y else x mockr/R/mock.R0000644000176200001440000000222313073253663012651 0ustar liggesusersextract_mocks <- function(dots, env) { lapply(stats::setNames(nm = names(dots)), function(qual_name) extract_mock(qual_name, dots[[qual_name]], env)) } extract_mock <- function(qual_name, dot, env) { name <- extract_mock_name(qual_name) check_mock(name, env) mock(name = name, new = lazyeval::lazy_eval(dot)) } extract_mock_name <- function(qual_name) { pkg_rx <- ".*[^:]" colons_rx <- "::(?:[:]?)" name_rx <- ".*" pkg_and_name_rx <- sprintf("^(?:(%s)%s)?(%s)$", pkg_rx, colons_rx, name_rx) pkg_name <- gsub(pkg_and_name_rx, "\\1", qual_name) if (pkg_name != "") { warningc("with_mock() cannot mock functions defined in other packages.") } name <- gsub(pkg_and_name_rx, "\\2", qual_name) name } check_mock <- function(name, env) { orig <- mget(name, envir = env, ifnotfound = list(NULL))[[1]] if (is.null(orig)) { stopc(name, " not found in environment ", environmentName(env), ".") } if (!is.function(orig)) { stopc(name, " is not a function in environment ", environmentName(env), ".") } } mock <- function(name, env, orig, new) { structure(list(name = as.name(name), new_value = new), class = "mock") } mockr/R/eval.R0000644000176200001440000000070513027325454012647 0ustar liggesusersevaluate_with_mock_env <- function(code, mock_env, .parent) { # Special treatment of last element, shortcut is important! if (length(code) == 0L) { return(invisible(NULL)) } old_parent <- parent.env(.parent) on.exit(parent.env(.parent) <- old_parent) parent.env(.parent) <- mock_env # Evaluate the code for (expression in code[-length(code)]) { lazyeval::lazy_eval(expression) } lazyeval::lazy_eval(code[[length(code)]]) } mockr/R/env.R0000644000176200001440000000266213073253663012517 0ustar liggesuserscheck_dots_env_ <- function(dots, .parent) { envs <- lapply(dots, "[[", "env") same <- vlapply(envs, identical, .parent) if (!all(same)) { stopc("Can only evaluate expressions in the parent environment.") } } create_mock_env_ <- function(..., .dots = NULL, .env, .parent) { dots <- lazyeval::all_dots(.dots, ..., all_named = TRUE) if (is.character(.env)) .env <- asNamespace(.env) new_funcs <- extract_new_funcs_(dots, .env) mock_env <- create_mock_env_with_old_funcs(new_funcs, .env, .parent) populate_env(mock_env, new_funcs) mock_env } extract_new_funcs_ <- function(dots, .env) { mocks <- extract_mocks(dots = dots, env = .env) new_func_names <- lapply(mocks, "[[", "name") new_funcs <- lapply(mocks, "[[", "new_value") names(new_funcs) <- new_func_names new_funcs } create_mock_env_with_old_funcs <- function(new_funcs, .env, .parent) { # retrieve all functions not mocked old_funcs <- as.list(.env) old_funcs <- old_funcs[vlapply(old_funcs, is.function)] old_funcs <- old_funcs[!(names(old_funcs) %in% names(new_funcs))] # query value visible from .parent to support nesting mock_env <- new.env(parent = parent.env(.parent)) old_funcs <- mget(names(old_funcs), .parent, inherits = TRUE) old_funcs <- lapply(old_funcs, `environment<-`, mock_env) populate_env(mock_env, old_funcs) mock_env } populate_env <- function(env, funcs) { lapply(names(funcs), function(x) env[[x]] <- funcs[[x]]) } mockr/R/mockr-package.R0000644000176200001440000000004113027325454014415 0ustar liggesusers#' @keywords internal "_PACKAGE" mockr/R/test.R0000644000176200001440000000021313027325454012671 0ustar liggesuserssome_symbol <- 42 mockee <- function() stop("Not mocking") mockee2 <- function() stop("Not mocking (2)") mockee3 <- function() mockee() mockr/R/with-mock.R0000644000176200001440000000510313073253663013622 0ustar liggesusers#' Mock functions in a package. #' #' Executes code after temporarily substituting implementations of package #' functions. This is useful for testing code that relies on functions that are #' slow, have unintended side effects or access resources that may not be #' available when testing. #' #' This works by adding a shadow environment as a parent of the environment #' in which the expressions are evaluated. Everything happens at the R level, #' but only functions in your own package can be mocked. #' Otherwise, the implementation is modeled after the original version in the #' `testthat` pacakge, which is now deprecated. #' #' @param ... `[any]`\cr named arguments redefine mocked functions, #' unnamed parameters will be evaluated after mocking the functions #' @param .env `[environment]`\cr the environment in which to patch the functions, #' defaults to [topenv()]. Usually doesn't need to be changed. #' @param .parent `[environment]`\cr the environment in which to evaluate the expressions, #' defaults to [parent.frame()]. Usually doesn't need to be changed. #' @return The result of the last unnamed parameter, visibility is preserved #' @references Suraj Gupta (2012): [How R Searches And Finds Stuff](http://blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/) #' @export #' @examples #' some_func <- function() stop("oops") #' some_other_func <- function() some_func() #' tester_func <- function() { #' with_mock( #' some_func = function() 42, #' some_other_func() #' ) #' } #' try(some_other_func()) #' tester_func() with_mock <- function(..., .parent = parent.frame(), .env = topenv(.parent)) { .dots <- lazyeval::lazy_dots(...) with_mock_(.dots = .dots, .parent = .parent, .env = .env) } with_mock_ <- function(..., .dots = NULL, .parent = parent.frame(), .env = topenv(.parent)) { dots <- lazyeval::all_dots(.dots, ...) check_dots_env_(dots, .parent) mock_env <- create_mock_env_(.dots = get_mock_dots(dots), .env = .env, .parent = .parent) evaluate_with_mock_env(get_code_dots(dots), mock_env, .parent) } get_mock_dots <- function(dots) { mock_qual_names <- names2(dots) if (all(mock_qual_names == "")) { warningc("Not mocking anything. Please use named arguments to specify the functions you want to mock.") list() } else { dots[mock_qual_names != ""] } } get_code_dots <- function(dots) { mock_qual_names <- names2(dots) if (all(mock_qual_names != "")) { warningc("Not evaluating anything. Please use unnamed arguments to specify expressions you want to evaluate.") list() } else { dots[mock_qual_names == ""] } } mockr/README.md0000644000176200001440000000515713100650314012645 0ustar liggesusers mockr [![Travis-CI Build Status](https://travis-ci.org/krlmlr/mockr.svg?branch=master)](https://travis-ci.org/krlmlr/mockr) [![Coverage Status](https://img.shields.io/codecov/c/github/krlmlr/mockr/master.svg)](https://codecov.io/github/krlmlr/mockr?branch=master) [![CRAN\_Status\_Badge](http://www.r-pkg.org/badges/version/mockr)](https://cran.r-project.org/package=mockr) ===================================================================================================================================================================================================================================================================================================================================================================================== The goal of mockr is to provide a drop-in replacement for `testthat::with_mock()` which will be deprecated in the next version of `testthat`. The only exported function, `with_mock()`, is modeled closely after the original implementation, but now only allows mocking functions in the package under test. In contrast to the original implementation, no fiddling with R's internals is needed, and the implementation plays well with byte-compiled code. There are some caveats, though: 1. Mocking external functions (in other packages) doesn't work anymore. This is by design. - If you need to mock an external function, write a wrapper. - If that external function is called by a third-party function, you'll need to perhaps mock that third-party function, or look for a different way of implementing this test or organizing your code. 2. You cannot refer to functions in your package via `your.package::` or `your.package:::` anymore, this is a limitation of the implementation. - Simply remove the `your.package:::`, your code and tests should run just fine without that. If you encounter other problems, please [file an issue](https://github.com/krlmlr/mockr/issues). Example ------- ``` r some_func <- function() stop("oops") some_other_func <- function() some_func() # Calling this function gives an error some_other_func() #> Error in some_func(): oops tester_func <- function() { # Here, we override the function that raises the error with_mock( some_func = function() 42, some_other_func() ) } # No error raised tester_func() #> [1] 42 # Mocking doesn't override functions in the same environment by design with_mock(some_func = function() 6 * 7, some_other_func()) #> Error in some_func(): oops ``` Installation ------------ Install from GitHub via ``` r devtools::install_github("krlmlr/mockr") ``` mockr/MD50000644000176200001440000000130513101111256011663 0ustar liggesusers766df3433ec0c83a8e7fbcf7addae677 *DESCRIPTION 850d7655614adc3178cfae8cf46ab51c *NAMESPACE 6eea78ac398cceda54e251366d322e06 *NEWS.md c5b5536380a4c90b9909175e3a0581d8 *R/env.R f1cf77c2e62c72378c2b097296f32760 *R/eval.R 0c1fee76c2d908089783c8897663b621 *R/mock.R b4fe12876c37f8a22cde06e9701eecf0 *R/mockr-package.R 701231d2de153d5e36a75510cff6208d *R/test.R f2cfe76e6aa0ac46c80746f3116b62c1 *R/utils.R e3db8c294f30e424731f977d256931db *R/with-mock.R 182c22d6407e43deedd34f741587ac5b *README.md a6dac867cf789b8e696ecb891765a6fa *man/mockr-package.Rd 28acb0f886d2e142f971d54542a46db1 *man/with_mock.Rd e759628ea2d2f3d56f4b132293f03284 *tests/testthat.R 2ecf46d8b347871b6072aa6edb65044a *tests/testthat/test-mock.R mockr/DESCRIPTION0000644000176200001440000000143713101111256013067 0ustar liggesusersPackage: mockr Title: Mocking in R Version: 0.1 Authors@R: person("Kirill", "Müller", role = c("aut", "cre"), email = "krlmlr+r@mailbox.org") Description: Provides a means to mock a package function, i.e., temporarily substitute it for testing. Designed as a drop-in replacement for 'testthat::with_mock()', which may break in R 3.4.0 and later. Imports: lazyeval Suggests: testthat, covr License: GPL-3 Encoding: UTF-8 LazyData: true Date: 2017-04-28 BugReports: https://github.com/krlmlr/mockr/issues URL: https://github.com/krlmlr/mockr, http://krlmlr.github.io/mockr RoxygenNote: 6.0.1 NeedsCompilation: no Packaged: 2017-04-29 08:10:46 UTC; muelleki Author: Kirill Müller [aut, cre] Maintainer: Kirill Müller Repository: CRAN Date/Publication: 2017-04-29 13:18:38 UTC mockr/man/0000755000176200001440000000000013027327167012150 5ustar liggesusersmockr/man/with_mock.Rd0000644000176200001440000000337413027327167014432 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/with-mock.R \name{with_mock} \alias{with_mock} \title{Mock functions in a package.} \usage{ with_mock(..., .parent = parent.frame(), .env = topenv(.parent)) } \arguments{ \item{...}{\code{[any]}\cr named arguments redefine mocked functions, unnamed parameters will be evaluated after mocking the functions} \item{.parent}{\code{[environment]}\cr the environment in which to evaluate the expressions, defaults to \code{\link[=parent.frame]{parent.frame()}}. Usually doesn't need to be changed.} \item{.env}{\code{[environment]}\cr the environment in which to patch the functions, defaults to \code{\link[=topenv]{topenv()}}. Usually doesn't need to be changed.} } \value{ The result of the last unnamed parameter, visibility is preserved } \description{ Executes code after temporarily substituting implementations of package functions. This is useful for testing code that relies on functions that are slow, have unintended side effects or access resources that may not be available when testing. } \details{ This works by adding a shadow environment as a parent of the environment in which the expressions are evaluated. Everything happens at the R level, but only functions in your own package can be mocked. Otherwise, the implementation is modeled after the original version in the \code{testthat} pacakge, which is now deprecated. } \examples{ some_func <- function() stop("oops") some_other_func <- function() some_func() tester_func <- function() { with_mock( some_func = function() 42, some_other_func() ) } try(some_other_func()) tester_func() } \references{ Suraj Gupta (2012): \href{http://blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/}{How R Searches And Finds Stuff} } mockr/man/mockr-package.Rd0000644000176200001440000000106613027325454015143 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/mockr-package.R \docType{package} \name{mockr-package} \alias{mockr} \alias{mockr-package} \title{mockr: Mocking in R} \description{ Provides a drop-In replacement for 'testthat::with_mock()'. } \seealso{ Useful links: \itemize{ \item \url{https://github.com/krlmlr/mockr} \item \url{http://krlmlr.github.io/mockr} \item Report bugs at \url{https://github.com/krlmlr/mockr/issues} } } \author{ \strong{Maintainer}: Kirill Müller \email{krlmlr+r@mailbox.org} } \keyword{internal}