mockery/ 0000755 0001762 0000144 00000000000 13201367135 011724 5 ustar ligges users mockery/inst/ 0000755 0001762 0000144 00000000000 13201337231 012673 5 ustar ligges users mockery/inst/doc/ 0000755 0001762 0000144 00000000000 13201337231 013440 5 ustar ligges users mockery/inst/doc/mocks-and-testthat.R 0000644 0001762 0000144 00000006565 13201337231 017311 0 ustar ligges users ## ----include=FALSE-------------------------------------------------------
library(mockery)
library(knitr)
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
## ----create_mock---------------------------------------------------------
m <- mock()
## ----return_values-------------------------------------------------------
m <- mock(1, 2, 3)
m()
m()
m()
## ----return_expression---------------------------------------------------
x <- 1
y <- 2
m <- mock(x + y)
m()
## ----cycle_no, eval=FALSE------------------------------------------------
# m <- mock(1, 2)
# m()
# #> [1] 1
# m()
# #> [1] 2
# m()
# #> Error: too many calls to mock object and cycle set to FALSE
## ----cycle_true----------------------------------------------------------
m <- mock(1, 2, cycle = TRUE)
m()
m()
m()
m()
## ----cycle_expression----------------------------------------------------
x <- 1
y <- 2
m <- mock(1, x + y, cycle = TRUE)
m()
m()
## ----cycle_expression_2nd------------------------------------------------
y <- 10
m()
m()
## ----return_expression_env-----------------------------------------------
x <- 1
y <- 2
e <- new.env()
m <- mock(x + y, envir = e, cycle = TRUE)
m()
e$x <- 10
m()
## ----with_mock, message=FALSE--------------------------------------------
library(testthat)
m <- mock(1)
f <- function (x) summary(x)
with_mock(f = m, {
expect_equal(f(iris), 1)
})
## ----expect_called-------------------------------------------------------
m <- mock(1, 2)
m()
expect_called(m, 1)
m()
expect_called(m, 2)
## ----expect_called_error, eval=FALSE-------------------------------------
# expect_called(m, 1)
# #> Error: mock object has not been called 1 time.
# expect_called(m, 3)
# #> Error: mock object has not been called 3 times.
## ----expect_call---------------------------------------------------------
m <- mock(1)
with_mock(summary = m, {
summary(iris)
})
expect_call(m, 1, summary(iris))
## ----call_doesnt_match, eval=FALSE---------------------------------------
# expect_call(m, 1, summary(x))
# #> Error: expected call summary(x) does not mach actual call summary(iris).
## ----expect_args---------------------------------------------------------
expect_args(m, 1, iris)
## ----expect_args_different, eval=FALSE-----------------------------------
# expect_args(m, 1, iris[-1, ])
# #> Error: arguments to call #1 not equal to expected arguments.
# #> Component 1: Attributes: < Component "row.names": Numeric: lengths (150, 149) differ >
# #> Component 1: Component 1: Numeric: lengths (150, 149) differ
# #> Component 1: Component 2: Numeric: lengths (150, 149) differ
# #> Component 1: Component 3: Numeric: lengths (150, 149) differ
# #> Component 1: Component 4: Numeric: lengths (150, 149) differ
# #> Component 1: Component 5: Lengths: 150, 149
# #> Component 1: Component 5: Lengths (150, 149) differ (string compare on first 149)
# #> Component 1: Component 5: 2 string mismatches
# #> expected argument list does not mach actual one.
## ----expect_args_named---------------------------------------------------
m <- mock(1)
with_mock(summary = m, {
summary(object = iris)
})
expect_args(m, 1, object = iris)
## ----expect_args_unnamed, eval=FALSE-------------------------------------
# expect_args(m, 1, iris)
# #> Error: arguments to call #1 not equal to expected arguments.
# #> names for target but not for current
# #> expected argument list does not mach actual one.
mockery/inst/doc/mocks-and-testthat.html 0000644 0001762 0000144 00000047036 13201337231 020052 0 ustar ligges users
Mocks
Mock object, which is a part of the mockery
package, started as an
extension to testthat's with_mock()
facility. Its main purpose was to simplify the replacement (mocking)
of a given function by means of with_mock
and the later
verification of actual calls invoked on the replacing function.
The mockery
package which provides its own stubbing facility, the
stub()
function. Here, however, we will look only at how mock()
can be used together with with_mock()
.
Mocks
Creating a mock
function
Mocking is a well-known technique when it comes to unit-testing and in
most languages there is some notion of a mock object. In R, however, the
natural equivalent of a mock object is a mock function - and this is
exactly what a call to mock()
will produce.
m <- mock()
Return values
Let's look at arguments accepted by the mock()
factory function. The
main is a list of values which will be returned upon subsequent calls to
m
.
m <- mock(1, 2, 3)
m()
#> [1] 1
m()
#> [1] 2
m()
#> [1] 3
mock()
can take also an expression which will be evaluated upon a call.
x <- 1
y <- 2
m <- mock(x + y)
m()
#> [1] 3
Cycling through return values
By default, if the total number of calls exceeds the number of defined
return values, the mock function will throw an exception. However, one
can also choose to cycle through the list of retun values by setting the
cycle
argument of mock()
to TRUE
.
m <- mock(1, 2)
m()
#> [1] 1
m()
#> [1] 2
m()
#> Error: too many calls to mock object and cycle set to FALSE
m <- mock(1, 2, cycle = TRUE)
m()
#> [1] 1
m()
#> [1] 2
m()
#> [1] 1
m()
#> [1] 2
If a return value is defined by an expression, this expression will be
evaluated each time a cycle reaches its position.
x <- 1
y <- 2
m <- mock(1, x + y, cycle = TRUE)
m()
#> [1] 1
m()
#> [1] 3
y <- 10
m()
#> [1] 1
m()
#> [1] 11
Evaluating expression in an environment of choice
Finally, one can specify the environment where the return expression is
evaluated.
x <- 1
y <- 2
e <- new.env()
m <- mock(x + y, envir = e, cycle = TRUE)
m()
#> [1] 3
e$x <- 10
m()
#> [1] 12
Integration with with_mock()
Simple integration
Using mock functions with testthat
's with_mock()
is pretty
straightforward.
library(testthat)
m <- mock(1)
f <- function (x) summary(x)
with_mock(f = m, {
expect_equal(f(iris), 1)
})
Verifying the number of calls
The mockery
package comes with a few additional expectations which
might turn out to be a very useful extension to testthat
's API. One
can for example verify the number and signature of calls invoked on a
mock function, as well as the values of arguments passed in those calls.
First, let's make sure the mocked function is called exactly as many
times as we expect. This can be done with expect_called()
.
m <- mock(1, 2)
m()
#> [1] 1
expect_called(m, 1)
m()
#> [1] 2
expect_called(m, 2)
And here is what happens when we get the number of calls wrong.
expect_called(m, 1)
#> Error: mock object has not been called 1 time.
expect_called(m, 3)
#> Error: mock object has not been called 3 times.
Verify the call signature
Another new expectation is expect_call()
which compares the signature
of the actual call as invoked on the mock function with the expected one.
It takes as arguments: the mock function, the call number, expected call.
m <- mock(1)
with_mock(summary = m, {
summary(iris)
})
#> [1] 1
expect_call(m, 1, summary(iris))
And here is what happens if the call doesn't match.
expect_call(m, 1, summary(x))
#> Error: expected call summary(x) does not mach actual call summary(iris).
Verify values of argument
Finally, one can verify whether the actual values of arguments passed
to the mock function match the expectation. Following the previous
example of summary(iris)
we can make sure that the object
parameter
passed to m()
was actually the iris
dataset.
expect_args(m, 1, iris)
Here is what happens if the value turns out to be different.
expect_args(m, 1, iris[-1, ])
#> Error: arguments to call #1 not equal to expected arguments.
#> Component 1: Attributes: < Component "row.names": Numeric: lengths (150, 149) differ >
#> Component 1: Component 1: Numeric: lengths (150, 149) differ
#> Component 1: Component 2: Numeric: lengths (150, 149) differ
#> Component 1: Component 3: Numeric: lengths (150, 149) differ
#> Component 1: Component 4: Numeric: lengths (150, 149) differ
#> Component 1: Component 5: Lengths: 150, 149
#> Component 1: Component 5: Lengths (150, 149) differ (string compare on first 149)
#> Component 1: Component 5: 2 string mismatches
#> expected argument list does not mach actual one.
If the call has been made with an explicit argument name the same has
to appear in expect_args()
.
m <- mock(1)
with_mock(summary = m, {
summary(object = iris)
})
#> [1] 1
expect_args(m, 1, object = iris)
Omitting the name results in an error.
expect_args(m, 1, iris)
#> Error: arguments to call #1 not equal to expected arguments.
#> names for target but not for current
#> expected argument list does not mach actual one.
Further reading
More information can be found in examples presented in manual pages
for ?mock
and ?expect_call
. Extensive information about testing
in R can be found in the documentation for the
testthat package.
mockery/inst/doc/mocks-and-testthat.Rmd 0000644 0001762 0000144 00000013777 13201337231 017635 0 ustar ligges users ---
title: 'Mocks: Integrating with `testthat`'
author: "Lukasz A. Bartnik"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Mocks: Integrating with testthat}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r include=FALSE}
library(mockery)
library(knitr)
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
```
Mock object, which is a part of the `mockery` package, started as an
extension to [testthat](github.com/hadley/testthat)'s `with_mock()`
facility. Its main purpose was to simplify the replacement (mocking)
of a given function by means of `with_mock` and the later
verification of actual calls invoked on the replacing function.
The `mockery` package which provides its own stubbing facility, the
`stub()` function. Here, however, we will look only at how `mock()`
can be used together with `with_mock()`.
## Mocks
### Creating a `mock` function
Mocking is a well-known technique when it comes to unit-testing and in
most languages there is some notion of a mock object. In R, however, the
natural equivalent of a mock object is a mock _function_ - and this is
exactly what a call to `mock()` will produce.
```{r create_mock}
m <- mock()
```
### Return values
Let's look at arguments accepted by the `mock()` _factory_ function. The
main is a list of values which will be returned upon subsequent calls to
`m`.
```{r return_values}
m <- mock(1, 2, 3)
m()
m()
m()
```
`mock()` can take also an expression which will be evaluated upon a call.
```{r return_expression}
x <- 1
y <- 2
m <- mock(x + y)
m()
```
### Cycling through return values
By default, if the total number of calls exceeds the number of defined
return values, the _mock_ function will throw an exception. However, one
can also choose to cycle through the list of retun values by setting the
`cycle` argument of `mock()` to `TRUE`.
```{r cycle_no, eval=FALSE}
m <- mock(1, 2)
m()
#> [1] 1
m()
#> [1] 2
m()
#> Error: too many calls to mock object and cycle set to FALSE
```
```{r cycle_true}
m <- mock(1, 2, cycle = TRUE)
m()
m()
m()
m()
```
If a return value is defined by an expression, this expression will be
evaluated each time a cycle reaches its position.
```{r cycle_expression}
x <- 1
y <- 2
m <- mock(1, x + y, cycle = TRUE)
m()
m()
```
```{r cycle_expression_2nd}
y <- 10
m()
m()
```
### Evaluating expression in an environment of choice
Finally, one can specify the environment where the return expression is
evaluated.
```{r return_expression_env}
x <- 1
y <- 2
e <- new.env()
m <- mock(x + y, envir = e, cycle = TRUE)
m()
e$x <- 10
m()
```
## Integration with `with_mock()`
### Simple integration
Using mock functions with `testthat`'s `with_mock()` is pretty
straightforward.
```{r with_mock, message=FALSE}
library(testthat)
m <- mock(1)
f <- function (x) summary(x)
with_mock(f = m, {
expect_equal(f(iris), 1)
})
```
### Verifying the number of calls
The `mockery` package comes with a few additional expectations which
might turn out to be a very useful extension to `testthat`'s API. One
can for example verify the number and signature of calls invoked on a
mock function, as well as the values of arguments passed in those calls.
First, let's make sure the mocked function is called exactly as many
times as we expect. This can be done with `expect_called()`.
```{r expect_called}
m <- mock(1, 2)
m()
expect_called(m, 1)
m()
expect_called(m, 2)
```
And here is what happens when we get the number of calls wrong.
```{r expect_called_error, eval=FALSE}
expect_called(m, 1)
#> Error: mock object has not been called 1 time.
expect_called(m, 3)
#> Error: mock object has not been called 3 times.
```
### Verify the call signature
Another new expectation is `expect_call()` which compares the signature
of the actual call as invoked on the mock function with the expected one.
It takes as arguments: the mock function, the call number, expected call.
```{r expect_call}
m <- mock(1)
with_mock(summary = m, {
summary(iris)
})
expect_call(m, 1, summary(iris))
```
And here is what happens if the call doesn't match.
```{r call_doesnt_match, eval=FALSE}
expect_call(m, 1, summary(x))
#> Error: expected call summary(x) does not mach actual call summary(iris).
```
### Verify values of argument
Finally, one can verify whether the actual values of arguments passed
to the mock function match the expectation. Following the previous
example of `summary(iris)` we can make sure that the `object` parameter
passed to `m()` was actually the `iris` dataset.
```{r expect_args}
expect_args(m, 1, iris)
```
Here is what happens if the value turns out to be different.
```{r expect_args_different, eval=FALSE}
expect_args(m, 1, iris[-1, ])
#> Error: arguments to call #1 not equal to expected arguments.
#> Component 1: Attributes: < Component "row.names": Numeric: lengths (150, 149) differ >
#> Component 1: Component 1: Numeric: lengths (150, 149) differ
#> Component 1: Component 2: Numeric: lengths (150, 149) differ
#> Component 1: Component 3: Numeric: lengths (150, 149) differ
#> Component 1: Component 4: Numeric: lengths (150, 149) differ
#> Component 1: Component 5: Lengths: 150, 149
#> Component 1: Component 5: Lengths (150, 149) differ (string compare on first 149)
#> Component 1: Component 5: 2 string mismatches
#> expected argument list does not mach actual one.
```
If the call has been made with an explicit argument name the same has
to appear in `expect_args()`.
```{r expect_args_named}
m <- mock(1)
with_mock(summary = m, {
summary(object = iris)
})
expect_args(m, 1, object = iris)
```
Omitting the name results in an error.
```{r expect_args_unnamed, eval=FALSE}
expect_args(m, 1, iris)
#> Error: arguments to call #1 not equal to expected arguments.
#> names for target but not for current
#> expected argument list does not mach actual one.
```
## Further reading
More information can be found in examples presented in manual pages
for `?mock` and `?expect_call`. Extensive information about testing
in R can be found in the documentation for the
[testthat](github.com/hadley/testthat) package.
mockery/tests/ 0000755 0001762 0000144 00000000000 13173435132 013067 5 ustar ligges users mockery/tests/testthat.R 0000644 0001762 0000144 00000000072 13173435132 015051 0 ustar ligges users library(testthat)
library(mockery)
test_check("mockery")
mockery/tests/testthat/ 0000755 0001762 0000144 00000000000 13201367135 014726 5 ustar ligges users mockery/tests/testthat/test_stub.R 0000644 0001762 0000144 00000020251 13201337016 017060 0 ustar ligges users testthat::context('stub')
a = 10
f = function(x) x
g = function(x) f(x) + a
test_that('stubs function with return value', {
# before stubbing
expect_equal(g(20), 30)
# when
stub(g, 'f', 100)
# then
expect_equal(g(20), 110)
})
test_that('values restored after test', {
expect_equal(f(15), 15)
expect_equal(g(15), 25)
})
test_that('stubs function with function', {
# given
a = 10
f = function(x) x
g = function(x) f(x) + a
# before stubbing
expect_equal(g(20), 30)
# when
stub(g, 'f', function(...) 500)
# then
expect_equal(g(10), 510)
})
test_that('stubs function from namespace', {
# given
f = function() testthat::capture_output(print('hello'))
# before stubbing
expect_true(grepl('hello', f()))
# when
stub(f, 'testthat::capture_output', 10)
# then
expect_equal(f(), 10)
})
test_that('does not stub other namespeaced functions', {
# given
f = function() {
a = testthat::capture_output(print('hello'))
b = testthat::is_null('not null')
return(c(a, b))
}
# when
stub(f, 'testthat::is_null', 'stubbed output')
# then
result = f()
expect_true(grepl('hello', result[1]))
expect_equal(result[2], 'stubbed output')
})
test_that('stub multiple functions', {
# given
f = function(x) x + 10
g = function(y) y + 20
h = function(z) f(z) + g(z)
# when
stub(h, 'f', 300)
stub(h, 'g', 500)
# then
expect_equal(h(1), 800)
})
test_that('stub multiple namespaced functions', {
# given
h = function(x) mockery::stub(x) + mockery::get_function_source(x)
# when
stub(h, 'mockery::stub', 300)
stub(h, 'mockery::get_function_source', 500)
# then
expect_equal(h(1), 800)
})
test_that('stub works with do.call', {
# given
f = function(x) x + 10
g = function(x) do.call('f', list(x))
# before stub
expect_equal(g(10), 20)
# stub
stub(g, 'f', 100)
# then
expect_equal(g(10), 100)
})
test_that('stub works with lapply', {
# given
f = function(x) x + 10
g = function(x) lapply(x, 'f')
l = list(1, 2, 3)
# before stub
expect_equal(g(l), list(11, 12, 13))
# stub
stub(g, 'f', 100)
# then
expect_equal(g(l), list(100, 100, 100))
})
test_that('stub works well with mock object', {
# given
f = function(x) x + 10
g = function(x) f(x)
mock_object = mock(100)
stub(g, 'f', mock_object)
# when
result = g(5)
# then
expect_equal(result, 100)
})
f = function(x) x + 10
g = function(x) f(x)
test_that('mock object returns value', {
mock_object = mock(1)
stub(g, 'f', mock_object)
expect_equal(g('anything'), 1)
expect_called(mock_object, 1)
expect_args(mock_object, 1, 'anything')
})
test_that('mock object multiple return values', {
mock_object = mock(1, "a", sqrt(3))
stub(g, 'f', mock_object)
expect_equal(g('anything'), 1)
expect_equal(g('anything'), "a")
expect_equal(g('anything'), sqrt(3))
})
test_that('mock object accessing values of arguments', {
mock_object <- mock()
mock_object(x = 1)
mock_object(y = 2)
expect_equal(length(mock_object), 2)
args <- mock_args(mock_object)
expect_equal(args[[1]], list(x = 1))
expect_equal(args[[2]], list(y = 2))
})
test_that('mock object accessing call expressions', {
mock_object <- mock()
mock_object(x = 1)
mock_object(y = 2)
expect_equal(length(mock_object), 2)
calls <- mock_calls(mock_object)
expect_equal(calls[[1]], quote(mock_object(x = 1)))
expect_equal(calls[[2]], quote(mock_object(y = 2)))
})
library(R6)
some_other_class = R6Class("some_class",
public = list(
external_method = function() {return('this is external output')}
)
)
some_class = R6Class("some_class",
public = list(
some_method = function() {return(some_function())},
some_method_prime = function() {return(some_function())},
other_method = function() {return('method in class')},
method_without_other = function() { self$other_method() },
method_with_other = function() {
other <- some_other_class$new()
other$external_method()
self$other_method()
}
)
)
# Calling function from R6 method
some_function = function() {return("called from within class")}
obj = some_class$new()
test_that('stub works with R6 methods', {
stub(obj$some_method, 'some_function', 'stub has been called')
expect_equal(obj$some_method(), 'stub has been called')
})
test_that('stub works with R6 methods that call internal methods in them', {
stub(obj$method_without_other, 'self$other_method', 'stub has been called')
expect_equal(obj$method_without_other(), 'stub has been called')
})
test_that('stub works with R6 methods that have other objects in them', {
stub(obj$method_with_other, 'self$other_method', 'stub has been called')
expect_equal(obj$method_with_other(), 'stub has been called')
})
test_that('R6 method does not stay stubbed', {
expect_equal(obj$some_method(), 'called from within class')
})
# Calling R6 method from function
other_func = function() {
obj = some_class$new()
return(obj$other_method())
}
test_that('stub works for stubbing R6 methods from within function calls', {
stub(other_func, 'obj$other_method', 'stubbed R6 method')
expect_equal(other_func(), 'stubbed R6 method')
})
test_that('stub does not stay in effect', {
expect_equal(other_func(), 'method in class')
})
test_that('stub out of namespaced functions', {
expect_true(grepl('hello', testthat::capture_output(print('hello'))))
stub(testthat::capture_output, 'paste0', 'stubbed function')
expect_equal(testthat::capture_output(print('hello')), 'stubbed function')
})
test_that('stub multiple namespaced and R6 functions from within test env', {
stub(testthat::capture_output, 'paste0', 'stub 1')
stub(obj$some_method, 'some_function', 'stub 2')
stub(obj$some_method_prime, 'some_function', 'stub 3')
stub(testthat::test_that, 'test_code', 'stub 4')
# all stubs are active
expect_equal(testthat::capture_output(print('hello')), 'stub 1')
expect_equal(obj$some_method(), 'stub 2')
expect_equal(obj$some_method_prime(), 'stub 3')
expect_equal(testthat::test_that('a', print), 'stub 4')
# non mocked R6 and namespaced functions work as expected
expect_equal(obj$other_method(), 'method in class')
testthat::expect_failure(expect_equal(4, 5))
})
h = function(x) 'called h'
g = function(x) h(x)
f = function(x) g(x)
test_that('use can specify depth of mocking', {
stub_string = 'called stub!'
stub(f, 'h', stub_string, depth=2)
expect_equal(f(1), stub_string)
})
h = function(x) 'called h'
g = function(x) h(x)
f = function(x) paste0(h(x), g(x))
test_that('mocked function is mocked at all depths', {
stub_string = 'called stub!'
stub(f, 'h', stub_string, depth=2)
expect_equal(f(1), 'called stub!called stub!')
})
h = function(x) 'called h'
g = function(x) h(x)
r = function(x) g(x)
f = function(x) paste0(h(x), r(x))
test_that('mocked function is mocked at all depths', {
stub_string = 'called stub!'
stub(f, 'h', stub_string, depth=3)
expect_equal(f(1), 'called stub!called stub!')
})
h = function(x) 'called h'
t = function(x) h(x)
g = function(x) h(x)
r = function(x) paste0(t(x), g(x))
u = function(x) paste0(h(x), g(x))
f = function(x) paste0(h(x), r(x), u(x))
t_env = new.env(parent=baseenv())
assign('h', h, t_env)
environment(t) = t_env
u_env = new.env(parent=baseenv())
assign('g', g, u_env)
assign('h', h, u_env)
environment(u) = u_env
f_env = new.env(parent=baseenv())
assign('u', u, f_env)
assign('h', h, f_env)
assign('r', r, f_env)
environment(f) = f_env
a = function(x) x
environment(a) = f_env
test_that('mocked function is mocked at all depths across paths', {
stub_string = 'called stub!'
stub(f, 'h', stub_string, depth=4)
expect_equal(f(1), 'called stub!called stub!called stub!called stub!called stub!')
})
.a = function(x) h(x)
test_that('mocks hidden functions', {
stub_string = 'called stub!'
stub(.a, 'h', stub_string, depth=4)
expect_equal(f(1), 'called stub!called stub!called stub!called stub!called stub!')
})
mockery/tests/testthat/test-mock-object.R 0000644 0001762 0000144 00000006631 13200334717 020230 0 ustar ligges users context("Mock objects")
test_that("mock single brackets", {
m <- mock(1)
m()
expect_equal(mock_calls(m)[[1]], bquote(m()))
})
test_that("mock length", {
m <- mock(1, cycle = TRUE)
expect_equal(length(m), 0)
m()
expect_equal(length(m), 1)
m()
m()
expect_equal(length(m), 3)
})
test_that("mock cyclic returns", {
m <- mock(1, cycle = TRUE)
expect_equal(lapply(1:10, m), as.list(rep(1, 10)))
m <- mock(1, 2, cycle = TRUE)
expect_equal(lapply(1:10, m), as.list(rep(1:2, 5)))
m <- mock(1, 2, 3, cycle = TRUE)
expect_equal(lapply(1:12, m), as.list(rep(1:3, 4)))
})
test_that("call list", {
m <- mock()
e <- environment(m)
with_mock(summary = m, {
summary(iris)
})
expect_true(exists('calls', envir = e))
expect_length(e$calls, 1)
expect_equal(e$calls[[1]], bquote(summary(iris)))
})
test_that("expect calls", {
m <- mock()
with_mock(summary = m, {
summary(iris)
})
expect_call(m, 1, summary(iris))
})
test_that("error for long call is formatted well", {
m <- mock()
mockery::stub(read.csv, "read.table", m)
read.csv('file.csv')
# test mock with mock, how crazy is that!
# we cannot call expect_failure because it messes up calls to expect()
# thus we intercept calls to expect() and later compare arguments
test_mock <- mock(TRUE, TRUE)
with_mock(expect = test_mock, {
expect_call(m, 1, x)
})
expect_called(test_mock, 2)
err <- paste0(
'expected call x does not mach actual call ',
'read.table(file = file, header = header, sep = sep, quote = quote, ',
'dec = dec, fill = fill, comment.char = comment.char).'
)
expect_args(test_mock, 2, FALSE, err)
})
test_that("empty return list", {
m <- mock()
expect_null(m())
})
test_that("too many calls", {
m <- mock(1)
expect_equal(1, m())
expect_failure(m(), "too many calls to mock object and cycle set to FALSE")
})
test_that("return expression", {
e <- new.env(parent = globalenv())
m <- mock(fun_x("a"),
fun_x("a") == "a",
envir = e, cycle = TRUE)
e$fun_x <- function(x)x
expect_equal(m(), "a")
expect_true(m())
e$fun_x <- function(x)"abc"
expect_equal(m(), "abc")
expect_false(m())
})
test_that("operator $ works for mock", {
m <- mock()
expect_equal(mock_calls(m), list())
expect_equal(mock_args(m), list())
})
test_that("arguments are recorded", {
m <- mock()
m(x = 1)
m(y = 2, z = iris)
expect_equal(length(m), 2)
expect_named(mock_args(m)[[1]], 'x')
expect_named(mock_args(m)[[2]], c('y', 'z'))
expect_equal(mock_args(m)[[1]]$x, 1)
expect_equal(mock_args(m)[[2]]$y, 2)
expect_equal(mock_args(m)[[2]]$z, iris)
})
test_that("expect args", {
m <- mock()
m(iris)
m(x = 1)
# compares values, not symbols
y <- 2
z <- iris
m(y = y, z = z)
expect_args(m, 1, iris)
expect_args(m, 2, x = 1)
expect_args(m, 3, y = 2, z = iris)
})
test_that("expect args in with_mock", {
m <- mock()
with_mock(lm = m, {
x <- iris
lm(Sepal.Width ~ Sepal.Length, data = x)
})
expect_args(m, 1, Sepal.Width ~ Sepal.Length, data = iris)
})
test_that("calls are counted", {
m <- mock()
expect_called(m, 0)
m()
expect_called(m, 1)
m()
expect_called(m, 2)
})
test_that("appropriate message if counts are missing", {
m <- mock()
expect_failure(expect_called(m, 1), "mock object has not been called 1 time")
expect_failure(expect_called(m, 2), "mock object has not been called 2 times")
})
mockery/NAMESPACE 0000644 0001762 0000144 00000000431 13173435132 013142 0 ustar ligges users # Generated by roxygen2: do not edit by hand
S3method(length,mock)
export(expect_args)
export(expect_call)
export(expect_called)
export(mock)
export(mock_args)
export(mock_calls)
export(stub)
importFrom(testthat,expect)
importFrom(testthat,expect_equal)
importFrom(testthat,fail)
mockery/NEWS 0000644 0001762 0000144 00000000274 13201337016 012421 0 ustar ligges users v 0.4.1
Fix bug whereby functions that begin with `.` don't have things mocked out in
them.
v 0.4.0
Add support for stubbing depth greater than 1.
Add support for nested R6 classes.
mockery/R/ 0000755 0001762 0000144 00000000000 13201337231 012117 5 ustar ligges users mockery/R/mockery.R 0000644 0001762 0000144 00000001414 13173435132 013722 0 ustar ligges users #' R package to make mocking easier
#'
#' There are great tools for unit testing in R out there already but
#' they don't come with a lot of support for mock objects. This
#' package aims at fixing that.
#'
#' @docType package
#' @name mockery
#'
#' @examples
#' library(mockery)
#'
#' m <- mock(TRUE, FALSE, TRUE)
#'
#' # this will make summary call our mock function rather then
#' # UseMethod; thus, summary() will return values as above
#' stub(summary, 'UseMethod', m)
#'
#' summary(iris) # returns TRUE
#' summary(cars) # returns FALSE
#' summary(co2) # returns TRUE
#'
#' \dontrun{
#' library(testthat)
#'
#' m <- mock(TRUE)
#' f <- function() read.csv('data.csv')
#'
#' with_mock(read.csv = m, {
#' f()
#' expect_call(m, 1, read.csv('data.csv'))
#' })
#' }
NULL mockery/R/mock-object.R 0000644 0001762 0000144 00000007402 13173435132 014451 0 ustar ligges users
#' Create and query a mocked function.
#'
#' Mock object's primary use is to record calls that are made on the
#' mocked function.
#'
#' Optionally values/expressions can be passed via \code{...} for the
#' mock object to return them upon subsequent calls. Expressions are
#' evaluated in environment \code{envir} before being returned. If no
#' value is passed in \code{...} then \code{NULL} is returned.
#'
#' Passing an expression or a function call via \code{...} is also a
#' way to implement side effects: keep track of the state of code
#' under testing, throw an exception when a condition is met, etc.
#'
#' \code{mock_calls} and \code{mock_args} can be used to access the
#' list of calls made on a mocked function and a respective list of
#' values of arguments passed to each of these calls.
#'
#'
#' @param ... Values returned upon subsequent calls.
#' @param cycle Whether to cycle over the return values. If \code{FALSE},
#' will fail if called too many times.
#' @param envir Where to evaluate the expressions being returned.
#' @param m A \code{\link{mock}}ed function.
#' @param x A \code{\link{mock}}ed function.
#'
#' @return \code{mock()} returns a mocked function which can be then used
#' with \code{\link{with_mock}}.
#' @return \code{mock_args()} returns a \code{list} of \code{list}s
#' of argument values.
#' @return \code{mock_calls()} returns a \code{list} of \code{call}s.
#' @return \code{length.mock()} returns the number of calls invoked on \code{m}.
#'
#' @examples
#' library(testthat)
#'
#' m <- mock(1)
#' with_mock(summary = m, {
#' expect_equal(summary(iris), 1)
#' expect_called(m, 1)
#' expect_call(m, 1, summary(iris))
#' expect_args(m, 1, iris)
#' })
#'
#' # multiple return values
#' m <- mock(1, "a", sqrt(3))
#' with_mock(summary = m, {
#' expect_equal(summary(iris), 1)
#' expect_equal(summary(iris), "a")
#' expect_equal(summary(iris), 1.73, tolerance = .01)
#' })
#'
#' # side effects
#' m <- mock(1, 2, stop("error"))
#' with_mock(summary = m, {
#' expect_equal(summary(iris), 1)
#' expect_equal(summary(iris), 2)
#' expect_error(summary(iris), "error")
#' })
#'
#' # accessing call expressions
#' m <- mock()
#' m(x = 1)
#' m(y = 2)
#' expect_equal(length(m), 2)
#' calls <- mock_calls(m)
#' expect_equal(calls[[1]], quote(m(x = 1)))
#' expect_equal(calls[[2]], quote(m(y = 2)))
#'
#' # accessing values of arguments
#' m <- mock()
#' m(x = 1)
#' m(y = 2)
#' expect_equal(length(m), 2)
#' args <- mock_args(m)
#' expect_equal(args[[1]], list(x = 1))
#' expect_equal(args[[2]], list(y = 2))
#'
#'
#' @name mock
NULL
#' @export
#' @rdname mock
#'
#' @importFrom testthat fail
mock <- function (..., cycle = FALSE, envir = parent.frame()) {
stopifnot(is.environment(envir))
return_values <- eval(substitute(alist(...)))
return_values_env <- envir
call_no <- 0
calls <- list()
args <- list()
mock_impl <- function(...) {
call_no <<- call_no + 1
calls[[call_no]] <<- match.call()
args[[call_no]] <<- list(...)
if (length(return_values)) {
if (call_no > length(return_values) && !cycle)
fail("too many calls to mock object and cycle set to FALSE")
value <- return_values[[(call_no - 1) %% length(return_values) + 1]]
return(eval(value, envir = return_values_env))
}
# TODO maybe it should the mock object itself?
invisible(NULL)
}
class(mock_impl) <- 'mock'
mock_impl
}
#' @export
#' @rdname mock
mock_args <- function (m) {
stopifnot(is_mock(m))
environment(m)$args
}
#' @export
#' @rdname mock
mock_calls <- function (m) {
stopifnot(is_mock(m))
environment(m)$calls
}
is_mock <- function (object) inherits(object, 'mock')
#' @export
#' @rdname mock
length.mock <- function (x)
{
length(environment(x)$calls)
}
mockery/R/expectations.R 0000644 0001762 0000144 00000006200 13173435132 014755 0 ustar ligges users
#' Expectation: does the given call match the expected?
#'
#' Together with \code{\link{mock}} can be used to verify whether the
#' call expression (\code{\link{expect_call}}) and/or argument values
#' (\code{\link{expect_args}}) match the expected.
#'
#' With \code{expect_called} you can check how many times has the mock
#' object been called.
#'
#' @param mock_object A \code{\link{mock}} object.
#' @param n Call number or total number of calls.
#' @param expected_call Expected call expression; will be compared unevaluated.
#' @param ... Arguments as passed in a call.
#'
#' @examples
#' library(testthat)
#'
#' # expect call expression (signature)
#' m <- mock()
#' with_mock(summary = m, summary(iris))
#'
#' # it has been called once
#' expect_called(m, 1)
#'
#' # the first (and only) call's arguments matches summary(iris)
#' expect_call(m, 1, summary(iris))
#'
#' # expect argument value
#' m <- mock()
#' a <- iris
#' with_mock(summary = m, summary(object = a))
#' expect_args(m, 1, object = a)
#' # is an equivalent to ...
#' expect_equal(mock_args(m)[[1]], list(object = a))
#'
#' @name call-expectations
#'
NULL
#' @export
#' @rdname call-expectations
#'
#' @importFrom testthat expect
expect_call <- function (mock_object, n, expected_call) {
stopifnot(is_mock(mock_object))
expect(
0 < n && n <= length(mock_object),
sprintf("call number %s not found in mock object", toString(n))
)
expected_call <- substitute(expected_call)
mocked_call <- mock_calls(mock_object)[[n]]
format_call <- function (x) paste(trimws(deparse(x)), collapse = ' ')
expect(
identical(mocked_call, expected_call),
sprintf("expected call %s does not mach actual call %s.",
format_call(expected_call), format_call(mocked_call))
)
invisible(TRUE)
}
#' @export
#' @rdname call-expectations
#'
#' @importFrom testthat expect expect_equal
expect_args <- function (mock_object, n, ...)
{
stopifnot(is_mock(mock_object))
expect(
0 < n && n <= length(mock_object),
sprintf("arguments list number %s not found in mock object", toString(n))
)
expected_args <- list(...)
actual_args <- mock_args(mock_object)[[n]]
expect_equal(length(actual_args), length(expected_args),
info = 'number of expected args does not match the actual')
for (i in seq_along(actual_args)) {
expect_equal(
actual_args[[i]],
expected_args[[i]],
label = paste(ordinal(i), 'actual argument'),
expected.label = paste(ordinal(i), 'expected argument')
)
}
invisible(TRUE)
}
ordinal <- function (x)
{
stopifnot(is.integer(x), x > 0, length(x) == 1)
if (x %in% 11:13)
return(paste0(x, 'th'))
as_string <- as.character(x)
last_digit <- substring(as_string, nchar(as_string))
suffix <- switch(last_digit,
`1` = 'st',
`2` = 'nd',
`3` = 'rd',
'th')
paste0(as_string, suffix)
}
#' @export
#' @rdname call-expectations
expect_called <- function (mock_object, n)
{
stopifnot(is_mock(mock_object))
expect(
length(mock_object) == n,
sprintf("mock object has not been called %s time%s", toString(n),
(if(n>1) "s" else ""))
)
}
mockery/R/stub.R 0000644 0001762 0000144 00000012375 13201337016 013230 0 ustar ligges users
#' Replace a function with a stub.
#'
#' The result of calling \code{stub} is that, when \code{where}
#' is invoked and when it internally makes a call to \code{what},
#' \code{how} is going to be called instead.
#'
#' This is much more limited in scope in comparison to
#' \code{\link[testthat]{with_mock}} which effectively replaces
#' \code{what} everywhere. In other words, when using \code{with_mock}
#' and regardless of the number of intermediate calls, \code{how} is
#' always called instead of \code{what}. However, using this API,
#' the replacement takes place only for a single function \code{where}
#' and only for calls originating in that function.
#'
#'
#' @name stub
#' @rdname stub
NULL
# \code{remote_stub} reverses the effect of \code{stub}.
#' @param where Function to be called that will in turn call
#' \code{what}.
#' @param what Name of the function you want to stub out (a
#' \code{character} string).
#' @param how Replacement function (also a \code{\link{mock}} function)
#' or a return value for which a function will be created
#' automatically.
#' @param depth Specifies the depth to which the function should be stubbed
#'
#' @export
#' @rdname stub
#'
#' @examples
#' f <- function() TRUE
#' g <- function() f()
#' stub(g, 'f', FALSE)
#'
#' # now g() returns FALSE because f() has been stubbed out
#' g()
#'
`stub` <- function (where, what, how, depth=1)
{
# `where` needs to be a function
where_name <- deparse(substitute(where))
# `what` needs to be a character value
stopifnot(is.character(what), length(what) == 1)
test_env <- parent.frame()
tree <- build_function_tree(test_env, where, where_name, depth)
mock_through_tree(tree, what, how)
}
mock_through_tree <- function(tree, what, how) {
for (d in tree) {
for (parent in d) {
parent_env = parent[['parent_env']]
func_dict = parent[['funcs']]
for (func_name in ls(func_dict, all.names=TRUE)) {
func = func_dict[[func_name]]
func_env = new.env(parent = environment(func))
what <- override_seperators(what, func_env)
where_name <- override_seperators(func_name, parent_env)
if (!is.function(how)) {
assign(what, function(...) how, func_env)
} else {
assign(what, how, func_env)
}
environment(func) <- func_env
assign(where_name, func, parent_env)
}
}
}
}
override_seperators = function(name, env) {
for (sep in c('::', "\\$")) {
if (grepl(sep, name)) {
elements <- strsplit(name, sep)
mangled_name <- paste(elements[[1]][1], elements[[1]][2], sep='XXX')
if (sep == '\\$') {
sep <- '$'
}
stub_list <- c(mangled_name)
if ("stub_list" %in% names(attributes(get(sep, env)))) {
stub_list <- c(stub_list, attributes(get(sep, env))[['stub_list']])
}
create_new_name <- create_create_new_name_function(stub_list, env, sep)
assign(sep, create_new_name, env)
}
}
return(if (exists('mangled_name')) mangled_name else name)
}
create_create_new_name_function <- function(stub_list, env, sep)
{
force(stub_list)
force(env)
force(sep)
create_new_name <- function(pkg, func)
{
pkg_name <- deparse(substitute(pkg))
func_name <- deparse(substitute(func))
for(stub in stub_list) {
if (paste(pkg_name, func_name, sep='XXX') == stub) {
return(eval(parse(text = stub), env))
}
}
# used to avoid recursively calling the replacement function
eval_env = new.env(parent=parent.frame())
assign(sep, eval(parse(text=paste0('`', sep, '`'))), eval_env)
code = paste(pkg_name, func_name, sep=sep)
return(eval(parse(text=code), eval_env))
}
attributes(create_new_name) <- list(stub_list=stub_list)
return(create_new_name)
}
build_function_tree <- function(test_env, where, where_name, depth)
{
func_dict = new.env()
func_dict[[where_name]] = where
tree = list(
# one depth
list(
# one parent
list(parent_env=test_env, funcs=func_dict)
)
)
if (depth > 1) {
for (d in 2:depth) {
num_parents = 0
new_depth = list()
for (funcs in tree[[d - 1]]) {
parent_dict = funcs[['funcs']]
for (parent_name in ls(parent_dict, all.names=TRUE)) {
func_dict = new.env()
parent_env = environment(get(parent_name, parent_dict))
for (func_name in ls(parent_env, all.names=TRUE)) {
func = get(func_name, parent_env)
if (is.function(func)) {
func_dict[[func_name]] = func
}
}
new_parent = list(parent_env=parent_env, funcs=func_dict)
num_parents = num_parents + 1
new_depth[[num_parents]] = new_parent
}
}
tree[[d]] = new_depth
}
}
return(tree)
}
mockery/vignettes/ 0000755 0001762 0000144 00000000000 13201337231 013726 5 ustar ligges users mockery/vignettes/mocks-and-testthat.Rmd 0000644 0001762 0000144 00000013777 13173435132 020132 0 ustar ligges users ---
title: 'Mocks: Integrating with `testthat`'
author: "Lukasz A. Bartnik"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Mocks: Integrating with testthat}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r include=FALSE}
library(mockery)
library(knitr)
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
```
Mock object, which is a part of the `mockery` package, started as an
extension to [testthat](github.com/hadley/testthat)'s `with_mock()`
facility. Its main purpose was to simplify the replacement (mocking)
of a given function by means of `with_mock` and the later
verification of actual calls invoked on the replacing function.
The `mockery` package which provides its own stubbing facility, the
`stub()` function. Here, however, we will look only at how `mock()`
can be used together with `with_mock()`.
## Mocks
### Creating a `mock` function
Mocking is a well-known technique when it comes to unit-testing and in
most languages there is some notion of a mock object. In R, however, the
natural equivalent of a mock object is a mock _function_ - and this is
exactly what a call to `mock()` will produce.
```{r create_mock}
m <- mock()
```
### Return values
Let's look at arguments accepted by the `mock()` _factory_ function. The
main is a list of values which will be returned upon subsequent calls to
`m`.
```{r return_values}
m <- mock(1, 2, 3)
m()
m()
m()
```
`mock()` can take also an expression which will be evaluated upon a call.
```{r return_expression}
x <- 1
y <- 2
m <- mock(x + y)
m()
```
### Cycling through return values
By default, if the total number of calls exceeds the number of defined
return values, the _mock_ function will throw an exception. However, one
can also choose to cycle through the list of retun values by setting the
`cycle` argument of `mock()` to `TRUE`.
```{r cycle_no, eval=FALSE}
m <- mock(1, 2)
m()
#> [1] 1
m()
#> [1] 2
m()
#> Error: too many calls to mock object and cycle set to FALSE
```
```{r cycle_true}
m <- mock(1, 2, cycle = TRUE)
m()
m()
m()
m()
```
If a return value is defined by an expression, this expression will be
evaluated each time a cycle reaches its position.
```{r cycle_expression}
x <- 1
y <- 2
m <- mock(1, x + y, cycle = TRUE)
m()
m()
```
```{r cycle_expression_2nd}
y <- 10
m()
m()
```
### Evaluating expression in an environment of choice
Finally, one can specify the environment where the return expression is
evaluated.
```{r return_expression_env}
x <- 1
y <- 2
e <- new.env()
m <- mock(x + y, envir = e, cycle = TRUE)
m()
e$x <- 10
m()
```
## Integration with `with_mock()`
### Simple integration
Using mock functions with `testthat`'s `with_mock()` is pretty
straightforward.
```{r with_mock, message=FALSE}
library(testthat)
m <- mock(1)
f <- function (x) summary(x)
with_mock(f = m, {
expect_equal(f(iris), 1)
})
```
### Verifying the number of calls
The `mockery` package comes with a few additional expectations which
might turn out to be a very useful extension to `testthat`'s API. One
can for example verify the number and signature of calls invoked on a
mock function, as well as the values of arguments passed in those calls.
First, let's make sure the mocked function is called exactly as many
times as we expect. This can be done with `expect_called()`.
```{r expect_called}
m <- mock(1, 2)
m()
expect_called(m, 1)
m()
expect_called(m, 2)
```
And here is what happens when we get the number of calls wrong.
```{r expect_called_error, eval=FALSE}
expect_called(m, 1)
#> Error: mock object has not been called 1 time.
expect_called(m, 3)
#> Error: mock object has not been called 3 times.
```
### Verify the call signature
Another new expectation is `expect_call()` which compares the signature
of the actual call as invoked on the mock function with the expected one.
It takes as arguments: the mock function, the call number, expected call.
```{r expect_call}
m <- mock(1)
with_mock(summary = m, {
summary(iris)
})
expect_call(m, 1, summary(iris))
```
And here is what happens if the call doesn't match.
```{r call_doesnt_match, eval=FALSE}
expect_call(m, 1, summary(x))
#> Error: expected call summary(x) does not mach actual call summary(iris).
```
### Verify values of argument
Finally, one can verify whether the actual values of arguments passed
to the mock function match the expectation. Following the previous
example of `summary(iris)` we can make sure that the `object` parameter
passed to `m()` was actually the `iris` dataset.
```{r expect_args}
expect_args(m, 1, iris)
```
Here is what happens if the value turns out to be different.
```{r expect_args_different, eval=FALSE}
expect_args(m, 1, iris[-1, ])
#> Error: arguments to call #1 not equal to expected arguments.
#> Component 1: Attributes: < Component "row.names": Numeric: lengths (150, 149) differ >
#> Component 1: Component 1: Numeric: lengths (150, 149) differ
#> Component 1: Component 2: Numeric: lengths (150, 149) differ
#> Component 1: Component 3: Numeric: lengths (150, 149) differ
#> Component 1: Component 4: Numeric: lengths (150, 149) differ
#> Component 1: Component 5: Lengths: 150, 149
#> Component 1: Component 5: Lengths (150, 149) differ (string compare on first 149)
#> Component 1: Component 5: 2 string mismatches
#> expected argument list does not mach actual one.
```
If the call has been made with an explicit argument name the same has
to appear in `expect_args()`.
```{r expect_args_named}
m <- mock(1)
with_mock(summary = m, {
summary(object = iris)
})
expect_args(m, 1, object = iris)
```
Omitting the name results in an error.
```{r expect_args_unnamed, eval=FALSE}
expect_args(m, 1, iris)
#> Error: arguments to call #1 not equal to expected arguments.
#> names for target but not for current
#> expected argument list does not mach actual one.
```
## Further reading
More information can be found in examples presented in manual pages
for `?mock` and `?expect_call`. Extensive information about testing
in R can be found in the documentation for the
[testthat](github.com/hadley/testthat) package.
mockery/README.md 0000644 0001762 0000144 00000010405 13201337016 013176 0 ustar ligges users # mockery
[](https://travis-ci.org/n-s-f/mockery)
[](https://codecov.io/github/n-s-f/mockery?branch=master)
[](https://cran.r-project.org/package=mockery)
A mocking library for R.
### Installation
To install the latest CRAN release:
```.R
> install.packages('mockery')
```
To install directly from the source code in this github repository:
```.R
> # If you don't have devtools installed yet:
> install.packages('devtools')
>
> # Then:
> library('devtools')
> devtools::install_github('n-s-f/mockery')
```
### Testing
Mockery provides the capacity for stubbing out functions and for verifying
function calls during testing.
#### Stubbing
Mockery's `stub` function will let you stub out a function with another
function or simply a return value. Note that if you choose to replace the
function with another function, the signatures of the two functions should be
compatible.
##### Examples
```.R
g = function(y) y
f = function(x) g(x) + 1
test_that('demonstrate stubbing', {
# replaces 'g' with a function that always returns 100
# but only when called from f
stub(f, 'g', 100)
# this can also be written
stub(f, 'g', function(...) 100)
expect_equal(f(1), 101)
})
```
Stubbing works with classes of all descriptions and namespaced functions:
```.R
# this stubs the 'request_perform' function, but only
# for httr::get, and only when it is called from within this
# test function
stub(httr::GET, 'request_perform', 'some html')
# it is also possible to stub out a namespaced function call
stub(some_function, 'namespace::function', 'some return value')
```
This also works with R6 classes and methods.
###### Depth
It's possible to specify the depth of stubbing. This is useful if you
want to stub a function that isn't called directly by the function you call in
your test, but is instead called by a function that that function calls.
In the example below, the function `g` is both called directly from `r`, which
we call from the test, and from `f`, which `r` calls. By specifying a depth of
2, we tell mockery to stub `g` in both places.
```.R
g = function(y) y
f = function(x) g(x) + 1
r = function(x) g(x) + f(x)
test_that('demonstrate stubbing', {
stub(r, 'g', 100, depth=2)
expect_equal(r(1), 201)
})
```
For more examples, please see the test code contained in this repository.
##### Comparison to with_mock
Mockery's `stub` function has similar functionality to testthat's `with_mock`.
There are several use cases in which mockery's `stub` function will work, but
testthat's `with_mock` will not.
First, unlike `with_mock`, it seamlessly allows for mocking out primitives.
Second, it is easy to stub out functions from base R packages with mockery's `stub`.
Because of how `with_mock` works, you can get into trouble if you mock such functions
that the JIT compiler might try to use. These kinds of problems are avoided by `stub`'s
design. As of version 2.0.0 of testthat, it will be impossible to mock functions from
base R packages `with_mock`.
The functionality of `stub` is just slightly different than that of `with_mock`. Instead
of mocking out the object of interest for the duration of some code block, it mocks it
out only when it is called from a specified function.
#### Mocking
Mock objects allow you to specify the behavior of the function you've stubbed
out while also verifying how that function was used.
```.R
g = function(y) y
f = function(x) g(x) + 1
test_that('demonstrate mock object usage', {
# mocks can specify behavior
mock = mock(100)
stub(f, 'g', mock)
result = g(5)
expect_equal(result, 101)
# and allow you to make assertions on the mock was treated
expect_called(mock, 1)
expect_args(mock, 5)
})
```
You can also specify multiple return values
```.R
mock = mock(1, "a", sqrt(3))
```
and access the arguments with which it was called.
```.R
mock <- mock()
mock(x = 1)
mock(y = 2)
expect_equal(length(mock), 2)
args <- mock_args(mock)
expect_equal(args[[1]], list(x = 1))
expect_equal(args[[2]], list(y = 2))
```
---
Please report bugs and feature requests through github issues.
mockery/MD5 0000644 0001762 0000144 00000002116 13201367135 012234 0 ustar ligges users e732efce7de7dbcc8724f7f2dbcfb1a8 *DESCRIPTION
3d598f5220436233590531b1de2f14b4 *LICENSE
7d0483b34bc263b9accf579e55eafe60 *NAMESPACE
ab111abd065205444612fd3a6316fb53 *NEWS
29fab533619bbf0b79f008f4c5d81d3f *R/expectations.R
6bd1b03af221e687d19b6d9abec4aa01 *R/mock-object.R
0cfe2a6243fd4bb782f5243bef1681dd *R/mockery.R
966b285097e526a71e5ab3300c416ba5 *R/stub.R
69537e7efbb09e3ad6d0e9c0d3da57c6 *README.md
dc43b969e6b3e7baeb161990a9a9a773 *build/vignette.rds
03f278824fd4bba9adcb6aa16815e422 *inst/doc/mocks-and-testthat.R
710dff0033da00ba9514450a0c4f7445 *inst/doc/mocks-and-testthat.Rmd
30cdc4c20e7e53d9312b1611486c21b8 *inst/doc/mocks-and-testthat.html
2a44bbe6cf2f5f7f420d88ecc506ce86 *man/call-expectations.Rd
e8ebb1f75ae4033bec35f85dff80a1e1 *man/mock.Rd
307d59ba3a7efe7341616c00548a93ab *man/mockery.Rd
94bfd4dda4facc03b153da7b97336323 *man/stub.Rd
aa03655779278d76db7f4ee3d7e1712c *tests/testthat.R
146022703ddd614dc91c4054075d7947 *tests/testthat/test-mock-object.R
ca068140f0dae8f7b08366b5796b9c3f *tests/testthat/test_stub.R
710dff0033da00ba9514450a0c4f7445 *vignettes/mocks-and-testthat.Rmd
mockery/build/ 0000755 0001762 0000144 00000000000 13201337231 013015 5 ustar ligges users mockery/build/vignette.rds 0000644 0001762 0000144 00000000342 13201337231 015353 0 ustar ligges users ‹ m‘Û
‚@†ÇC–Buëèt-BDÑE·‹n*é: ÝõäÙX¹9ìÌøþ9°g TÐT
hh6ƒ|A®€&í«¼¯•ËDä"¯†Þ1$ÊÙ·ÔÆÙ
äqÉ0±S§˜8½Fâ×#YÌ3 [ާží7óaÉtõG3,ç•”hêóQûü×+$™íø½.Ê^3`ŒŽÑƒ4ã}ÝSŠß‹vðƒî¨åþòú7Ë¢öúæí=(4dò aÆ*yP+bȼKIú¶ï>ƨ¬ã mockery/DESCRIPTION 0000644 0001762 0000144 00000002270 13201367135 013433 0 ustar ligges users Package: mockery
Version: 0.4.1
Title: Mocking Library for R
Description:
The two main functionalities of this package are creating mock
objects (functions) and selectively intercepting calls to a given
function that originate in some other function. It can be used
with any testing framework available for R. Mock objects can
be injected with either this package's own stub() function or a
similar with_mock() facility present in the testthat package.
Authors@R: c(
person("Noam", "Finkelstein", role = c("aut", "cre"),
email = "noam.finkelstein@jhu.edu"),
person("Lukasz", "Bartnik", rol = c("aut"),
email = "l.bartnik@gmail.com")
)
URL: https://github.com/n-s-f/mockery
BugReports: https://github.com/n-s-f/mockery/issues
Imports: testthat
Suggests: knitr, rmarkdown (>= 1.0)
License: MIT + file LICENSE
Collate: 'expectations.R' 'mockery.R' 'mock-object.R' 'stub.R'
VignetteBuilder: knitr
RoxygenNote: 6.0.1
NeedsCompilation: no
Packaged: 2017-11-10 14:58:33 UTC; noam
Author: Noam Finkelstein [aut, cre],
Lukasz Bartnik [aut]
Maintainer: Noam Finkelstein
Repository: CRAN
Date/Publication: 2017-11-10 18:22:21 UTC
mockery/man/ 0000755 0001762 0000144 00000000000 13200334717 012476 5 ustar ligges users mockery/man/mock.Rd 0000644 0001762 0000144 00000005213 13200334717 013717 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/mock-object.R
\name{mock}
\alias{mock}
\alias{mock}
\alias{mock_args}
\alias{mock_calls}
\alias{length.mock}
\title{Create and query a mocked function.}
\usage{
mock(..., cycle = FALSE, envir = parent.frame())
mock_args(m)
mock_calls(m)
\method{length}{mock}(x)
}
\arguments{
\item{...}{Values returned upon subsequent calls.}
\item{cycle}{Whether to cycle over the return values. If \code{FALSE},
will fail if called too many times.}
\item{envir}{Where to evaluate the expressions being returned.}
\item{m}{A \code{\link{mock}}ed function.}
\item{x}{A \code{\link{mock}}ed function.}
}
\value{
\code{mock()} returns a mocked function which can be then used
with \code{\link{with_mock}}.
\code{mock_args()} returns a \code{list} of \code{list}s
of argument values.
\code{mock_calls()} returns a \code{list} of \code{call}s.
\code{length.mock()} returns the number of calls invoked on \code{m}.
}
\description{
Mock object's primary use is to record calls that are made on the
mocked function.
}
\details{
Optionally values/expressions can be passed via \code{...} for the
mock object to return them upon subsequent calls. Expressions are
evaluated in environment \code{envir} before being returned. If no
value is passed in \code{...} then \code{NULL} is returned.
Passing an expression or a function call via \code{...} is also a
way to implement side effects: keep track of the state of code
under testing, throw an exception when a condition is met, etc.
\code{mock_calls} and \code{mock_args} can be used to access the
list of calls made on a mocked function and a respective list of
values of arguments passed to each of these calls.
}
\examples{
library(testthat)
m <- mock(1)
with_mock(summary = m, {
expect_equal(summary(iris), 1)
expect_called(m, 1)
expect_call(m, 1, summary(iris))
expect_args(m, 1, iris)
})
# multiple return values
m <- mock(1, "a", sqrt(3))
with_mock(summary = m, {
expect_equal(summary(iris), 1)
expect_equal(summary(iris), "a")
expect_equal(summary(iris), 1.73, tolerance = .01)
})
# side effects
m <- mock(1, 2, stop("error"))
with_mock(summary = m, {
expect_equal(summary(iris), 1)
expect_equal(summary(iris), 2)
expect_error(summary(iris), "error")
})
# accessing call expressions
m <- mock()
m(x = 1)
m(y = 2)
expect_equal(length(m), 2)
calls <- mock_calls(m)
expect_equal(calls[[1]], quote(m(x = 1)))
expect_equal(calls[[2]], quote(m(y = 2)))
# accessing values of arguments
m <- mock()
m(x = 1)
m(y = 2)
expect_equal(length(m), 2)
args <- mock_args(m)
expect_equal(args[[1]], list(x = 1))
expect_equal(args[[2]], list(y = 2))
}
mockery/man/stub.Rd 0000644 0001762 0000144 00000002510 13200334717 013740 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/stub.R
\name{stub}
\alias{stub}
\alias{stub}
\title{Replace a function with a stub.}
\usage{
stub(where, what, how, depth = 1)
}
\arguments{
\item{where}{Function to be called that will in turn call
\code{what}.}
\item{what}{Name of the function you want to stub out (a
\code{character} string).}
\item{how}{Replacement function (also a \code{\link{mock}} function)
or a return value for which a function will be created
automatically.}
\item{depth}{Specifies the depth to which the function should be stubbed}
}
\description{
The result of calling \code{stub} is that, when \code{where}
is invoked and when it internally makes a call to \code{what},
\code{how} is going to be called instead.
}
\details{
This is much more limited in scope in comparison to
\code{\link[testthat]{with_mock}} which effectively replaces
\code{what} everywhere. In other words, when using \code{with_mock}
and regardless of the number of intermediate calls, \code{how} is
always called instead of \code{what}. However, using this API,
the replacement takes place only for a single function \code{where}
and only for calls originating in that function.
}
\examples{
f <- function() TRUE
g <- function() f()
stub(g, 'f', FALSE)
# now g() returns FALSE because f() has been stubbed out
g()
}
mockery/man/call-expectations.Rd 0000644 0001762 0000144 00000002542 13200334717 016407 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/expectations.R
\name{call-expectations}
\alias{call-expectations}
\alias{expect_call}
\alias{expect_args}
\alias{expect_called}
\title{Expectation: does the given call match the expected?}
\usage{
expect_call(mock_object, n, expected_call)
expect_args(mock_object, n, ...)
expect_called(mock_object, n)
}
\arguments{
\item{mock_object}{A \code{\link{mock}} object.}
\item{n}{Call number or total number of calls.}
\item{expected_call}{Expected call expression; will be compared unevaluated.}
\item{...}{Arguments as passed in a call.}
}
\description{
Together with \code{\link{mock}} can be used to verify whether the
call expression (\code{\link{expect_call}}) and/or argument values
(\code{\link{expect_args}}) match the expected.
}
\details{
With \code{expect_called} you can check how many times has the mock
object been called.
}
\examples{
library(testthat)
# expect call expression (signature)
m <- mock()
with_mock(summary = m, summary(iris))
# it has been called once
expect_called(m, 1)
# the first (and only) call's arguments matches summary(iris)
expect_call(m, 1, summary(iris))
# expect argument value
m <- mock()
a <- iris
with_mock(summary = m, summary(object = a))
expect_args(m, 1, object = a)
# is an equivalent to ...
expect_equal(mock_args(m)[[1]], list(object = a))
}
mockery/man/mockery.Rd 0000644 0001762 0000144 00000001502 13200334717 014434 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/mockery.R
\docType{package}
\name{mockery}
\alias{mockery}
\alias{mockery-package}
\title{R package to make mocking easier}
\description{
There are great tools for unit testing in R out there already but
they don't come with a lot of support for mock objects. This
package aims at fixing that.
}
\examples{
library(mockery)
m <- mock(TRUE, FALSE, TRUE)
# this will make summary call our mock function rather then
# UseMethod; thus, summary() will return values as above
stub(summary, 'UseMethod', m)
summary(iris) # returns TRUE
summary(cars) # returns FALSE
summary(co2) # returns TRUE
\dontrun{
library(testthat)
m <- mock(TRUE)
f <- function() read.csv('data.csv')
with_mock(read.csv = m, {
f()
expect_call(m, 1, read.csv('data.csv'))
})
}
}
mockery/LICENSE 0000644 0001762 0000144 00000000076 13173435132 012735 0 ustar ligges users YEAR: 2016
COPYRIGHT HOLDER: Noam Finkelstein, Lukasz Bartnik