Test interaction with users in R package
Asked Answered
T

1

7

I am developing an R package and one of the function implements interaction with users through standard input via readline. I now wonder how to test the behavior of this function, preferably with testthat library.

It seems test_that function assumes the answer is "" for user-input. I wish I could test the behavior conditional of various answers users may type in.

Below is a small example code. In the actual development, the marryme function is defined in a separate file and exported to the namespace. devtools::test() gets me an error on the last line because the answer never becomes yes. I would like to test if the function correctly returns true when user types "y".

library(testthat)

test_that("input", {
  marryme <- function() {
    ans <- readline("will you marry me? (y/n) > ")
    return(ans == "y")
  }

  expect_false(marryme())  # this is good
  expect_true(marryme())   # this is no good
})
Toogood answered 29/12, 2016 at 1:52 Comment(2)
Split marryme into two functions. Put everything except readline in a function you can test and call that function with a wrapper function that contains readline. Btw., I'm not a fan of using readline for user input.Playlet
Thanks, @Roland. What would you suggest as an alternative for readline?Toogood
A
8

Use readLines() with a custom connection

By using readLines() instead of readline(), you can define the connection, which allows you to customize it using global options.

There are two steps that you need to do:

  1. set a default option in your package in zzz.R that points to stdin:

    .onAttach <- function(libname, pkgname){
      options(mypkg.connection = stdin())
    }
    
  2. In your function, change readline to readLines(n = 1) and set the connection in readLines() to getOption("mypkg.connection")

Example

Based on your MWE:


    library(testthat)

    options(mypkg.connection = stdin())

    marryme <- function() {
      cat("will you marry me? (y/n) > ")
      ans <- readLines(con = getOption("mypkg.connection"), n = 1)
      cat("\n")
      return(ans == "y")
    }

    test_that("input", {

      f <- file()
      options(mypkg.connection = f)
      ans <- paste(c("n", "y"), collapse = "\n") # set this to the number of tests you want to run
      write(ans, f)

      expect_false(marryme())  # this is good
      expect_true(marryme())   # this is no good
      # reset connection
      options(mypkg.connection = stdin())
      # close the file
      close(f)
    })
#> will you marry me? (y/n) > 
#> will you marry me? (y/n) >
Adamo answered 11/10, 2017 at 19:39 Comment(4)
This looks good for a single prompt, but I don't immediately see how to extend it to several prompts without having to set a litany of options for each input line...Salmon
@MichaelChirico, I only use two options() calls per test suite, no matter how many input lines. The challenge is keeping track of the answer stack (in the ans variable).Adamo
I am getting this error like that. Error in nsenv[[f_name]](dirname(ns_path), package) : unbenutzte Argumente (dirname(ns_path), package) Ruft auf: suppressPackageStartupMessages ... <Anonymous> -> load_code -> <Anonymous> -> run_pkg_hookNuzzle
I added libname, pkgname to the .onAttach method. Otherwise it throws the error mentioned in the comments.Nuzzle

© 2022 - 2024 — McMap. All rights reserved.