6

I am currently using R6 classes in a project.

I would like to write unit tests that also test the functionality of private methods that I am using (preferably by not going through the more complicated public methods that are using these private methods).

However, I can't access seem to access the private methods.

How do I best do that?

Thanks!

Holger Hoefling
  • 388
  • 3
  • 13
  • 3
    Shouldn't you be testing your private methods as they would normally be called from client code -- indirectly (e.g. from within public methods of your class), and *not* directly? The whole point of making methods and data members private is so that they aren't accessible from outside of the object instance. – nrussell May 12 '16 at 14:49
  • 3
    Thanks @nrussell. For me, the point of writing unit-tests is in order to ensure that all functionality works as intended. For that purposes, unit-tests are best written at the lowest level possible. Writing a unit test for a public method that does something much more complicated (e.g. calls the private method hundreds of times in various settings) makes running the test and debugging failures much more complicated. – Holger Hoefling May 13 '16 at 09:06

3 Answers3

15

Here is a solution that does not require environment hacking or altering the class you want to test, but instead creating a new class that does the testing for you.

In R6, derived classes have access to private Methods of their base classes (unlike in C++ or Java where you need the protected keyword to archieve the same result). Therefore, you can write a TesterClass that derives from the class you want to test. For example:

ClassToTest <- R6::R6Class(
  private = list(
    privateMember = 7,
    privateFunction = function(x) {
      return(x * private$privateMember)
    }
  )
)

TesterClass <- R6::R6Class(
  inherit = ClassToTest,
  public = list(
    runTest = function(x = 5) {
      if (x * private$privateMember != private$privateFunction(x))
        cat("Oops. Somethig is wrong\n")
      else
        cat("Everything is fine\n")
    }
  )
)

t <- TesterClass$new()
t$runTest()
#> Everything is fine

One advantage of this approach is that you can save detailed test results in the TesterClass.

Gregor de Cillia
  • 7,397
  • 1
  • 26
  • 43
3

There is currently a way to access the private environment of an R6 object. However, since this is reaching into an object in an undocumented way, it may break in the future... I don't think that will happen any time soon though.

# Gets private environment from an R6 object
get_private <- function(x) {
  x[['.__enclos_env__']]$private
}

A <- R6::R6Class("A",
  private = list(x = 1)
)

a <- A$new()

get_private(a)$x
# [1] 1
Bárbara Borges
  • 889
  • 7
  • 15
1

you can add a helper method get to your class:

...

A <- R6::R6Class(
  "A",
  private = list(
    private_print = function(){print("Ola")}
  ),
  public = list(
    get = function(name=NULL){
      # recursion
      if( length(name)>1 ){
        tmp <- lapply(name, self$get)
        names(tmp) <- name
        return(tmp)
      }
      if(is.null(name)){
        self$message("no input, returning NULL")
        return(NULL)
      }
      # self
      if(name=="self"){
        return(self)
      }
      # in self
      if( name %in% names(self) ){
        return(base::get(name, envir=self))
      }
      # private or in private
      if( exists("private") ){
        if(name=="private"){
          return(private)
        }else if(name %in% names(private) ){
          return(base::get(name, envir=private))
        }
      }
      # else
      self$message("name not found")
      return(NULL)
    }
  )
)

...

Than use it like this:

a <- A$new()

a$get("private_print")()
## [1] "Ola"
petermeissner
  • 12,234
  • 5
  • 63
  • 63