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!')
})

test_that("Does not error if function contains double quoted assignment functions", {
    f <- function(x, nms) {
      base::names(x) <- base::tolower(nms)
      x
    }

    stub(f, "base::tolower", toupper)
    expect_equal(f(1, "b"), c(B = 1))

    stub(f, "base::names<-", function(x, value) stats::setNames(x, "d"))
    expect_equal(f(1, "b"), c(d = 1))
})

h = function(x) x
g = function(x) h(x)
f = function(x) g(x)

locked_f_env = new.env(parent = baseenv())
assign('g', g, locked_f_env)
assign('h', h, locked_f_env)
environment(f) <- locked_f_env
lockEnvironment(locked_f_env, bindings=TRUE)

test_that('stubs locked functions', {
    stub_string = 'called stub!'
    stub(f, 'h', stub_string, depth=2)
    expect_equal(f('not stub'), 'called stub!')
})
