Как имитировать функции из базы?

Я testthat функцию из базы в своем коде, и я хочу издеваться над этой функцией в своем testthat модуле testthat.

Как я могу это сделать?

library(testthat)

my.func <- function() {
  return(Sys.info()["sysname"])   # e. g. "Linux"
}

my.func()
# sysname 
# "Linux" 

test_that("base function can be mocked",
  with_mock(
    Sys.info = function() return(list(sysname = "Clever OS")),  # see edit 2 !!!
    expect_equal(my.func(), "Clever OS", fixed = TRUE)
  )
)
# Error: Test failed: 'base function can be mocked'
# * my.func() not equal to "Clever OS".

?with_mock говорит:

Функции в базовых пакетах нельзя издеваться, но это можно легко обойти, указав функцию обертки.

Я мог бы инкапсулировать вызов базовой функции Sys.info() с помощью функции-оболочки, которую я вызываю из my.func но пусть предполагается, что я не могу этого сделать, потому что я тестирую функцию из пакета, который я не могу изменить...

Любое решение для этого?

Я использую R3.4.4 64 бит на Ubuntu 14.04 с testthat 2.0.0.9000.

Изменить 1:

С помощью

'base::Sys.info' = function() return(list(sysname = "Clever OS"))

приводит к ошибке testthat msg:

Невозможно высмеять функции в базовых пакетах (базовых)

Редактировать 2: Поскольку @suren показывает в своем ответе, мой пример кода здесь неверен (функция издевательства возвращает другой класс, а затем оригинал :-(

Правильная функция mock должна быть: Sys.info = function() return(c(sysname = "Clever OS"))

Ответы

Ответ 1

Сообщение об ошибке - my.func(), не равное "Умной ОС".. Причина в том, что Sys.info возвращает именованный вектор символов, а ваша издевательская функция - list.

Просто измените насмешливую функцию и ожидаемое значение, и оно работает:

test_that("base function can be mocked",
  with_mock(
    Sys.info = function() return(c(sysname = "Clever OS")),
    expect_equal(my.func(), c(sysname = "Clever OS"), fixed = TRUE)
  )
)

Это работает даже внутри пакета.

Примечание. Отказывание базовых функций не должно работать в соответствии с помощью with_mock но оно выполняется (по крайней мере, на данный момент).

Следующий (my.func() $ 'sysname'), похоже, передает тест с исходным кодом из вопроса.

test_that("base function can be mocked",
          with_mock(
            Sys.info = function() return(list(sysname = "Clever OS")), 
            expect_equal(my.func()$'sysname', "Clever OS", fixed = TRUE)
          )
)

Кроме того, у вас есть список, в котором строка в expect_equal

test_that("base function can be mocked",
          with_mock(
            Sys.info = function() return(list(sysname = "Clever OS")),  
            expect_equal(my.func(), list('sysname' = "Clever OS"), fixed = TRUE)
          )
)

Ответ 2

Предупреждение об издевательских базовых функциях с помощью with_mock :

Несмотря на то, что издевательство над базовыми функциями может работать в случае моего вопроса, а принятый ответ @Suren вызывает много проблем вокруг with_mock в testthat пакете, чтобы отказаться от фальсификации функций базового пакета (или даже функций вне тестируемого пакета), например

testthat 2.0.0 - Нарушение API-интерфейса

  • "Невозможно высмеять функции в базовых пакетах": вы больше не можете использовать with_mock() для издевательства функций в базовых пакетах, потому что это больше не работает в R-девеле из-за изменений с помощью компилятора байтового кода. mockr этого я рекомендую использовать mockery или mockr.

Не допускайте издевательства над базовыми пакетами

Функция with_mock(), похоже, плохо взаимодействует с JIT-компилятором

  • Как я уже упоминал в hadley/testthat # 546 (комментарий), в издевательстве уже есть функция замены для to_mock, называемая stub, которая использует другой подход к издевательствам, который не ограничен проблемами, возникающими в других потоках. Если с_моклом ломается, я думаю, что этот пакет будет работать так же с насмешкой. Я также думаю, что издевательство было бы разумным местом для размещения with_mock, где он мог бы жить вместе с заглушкой по наследственным причинам.

Предотвращать с помощью__mock от касания базовых пакетов R