Using internal tests
Michal Czyz
2026-04-02
Source:vignettes/using_internal_tests.Rmd
using_internal_tests.RmdWhy to test inside function
Most of the functions have at least one function parameter that can
be controlled by the user. Usually the parameters have some limits –
they might be limited to some class or some particular
value. Or can take one of predefined values. If user passes wrong value
to the parameter, in most cases the function throws an error. Or we want
the function to throw an error. Default error messages do not look good,
or sometimes they are not very informative, so we rather want to use our
own standard error messages. For instance, look what happens when you
try to multiply character by numeric:
2 * "a"
[1m
[33mError
[39m in `2 * "a"`:
[22m
[33m!
[39m non-numeric argument to binary operator
How wonderful would it be if the message looked like:
[1m
[33mError
[39m:
[22m
[1m
[22m
[31m✖
[39m Cannot multiply
[34m<numeric>
[39m by
[34m<character>
[39m
So take home message is: we use internal tests to assure that user passes only allowed values to parameters. If they pass wrong values then we show nicely formatted and easy to understand error messages.
g6tr.ui tests
There is a huge amount of possible test that we can run. However,
some of them are more common. In g6tr.ui we focused on the
tests that we use very often. We wrapped them into test functions that
are easy to use, and have informative names. The most important is to
check arguments as soon as possible within function, so if anything is
wrong the function will fail quickly.
The whole function flow can be generalized as:
new_function <- function(arg_a, arg_b, arg_c){
#test arguments
check_class(arg_a, "character")
check_length(arg_b, arg_length = 2L)
check_scalar(arg_c, arg_class = "integer")
#...
#function body
#function output
}Our tests can be divided into two groups: single tests and complex tests. Complex tests are wrappers around more than one single test or test more than one thing at once or are wrappers allowing to run same test for multiple arguments.
Single tests
-
check_bool()– expects argument to be singleTRUEorFALSE; -
check_class()– expects argument to be of expectedclass; -
check_color()– checks if all the values incharactervector are valid color names or color hex values; -
check_file()– checks if a file exists; -
check_dir()– checks if a directory exists; -
check_length()– check if argument is of expected length; -
check_allowed_strings()– checks ifcharactervector contains only allowed strings; -
check_exact_strings()– checks ifcharactervector contains exactly the expected values; -
check_needed_strings()– checks if expected strings are present in `character vector; -
check_inequality()– checks if an argument fulfills inequality.
For all the simple tests (besides check_file and
check_dir) it is possible to pass additional message that
will be displayed when function throws an error.
Complex tests
-
check_scalar()– checks if argument is of expectedclassand has length 1; -
check_scalar_sign()– checks if argument is a numeric of length 1 and is negative/positive; -
check_strings()– wrapper overcheck_exact_strings,check_needed_stringsandcheck_allowed_strings; -
check_multiple()– allows running same test over multiple arguments. -
check_null_value()– checks if argument isNULLOR a certain value.
How Stuff Works
Easiest way to grasp how to use the functions is by looking at
examples. Below you will see examples for each of the functions. Some of
them have additional arguments like alt_null or
add_msg. In the examples we do not cover all the possible
use cases, so every time you should refer to function documentation and
examples where you will find all the necessary information.
check_bool
Use this if you expect single TRUE or
FALSE. As an alternative you can allow the argument to be
NULL.
lgl_v <- c(TRUE, FALSE)
nl1 <- NULL
tr1 <- TRUE
check_bool(x = lgl_v)
[1m
[33mError
[39m in `check_bool()`:
[22m
[1m
[22m
[33m!
[39m `lgl_v` should be either `TRUE` or `FALSE`
[31m✖
[39m `lgl_v` is a
[34m<logical>
[39m vector, but should be a scalar (single value)
check_bool(x = tr1) # returns invisible NULL
check_bool(nl1)
[1m
[33mError
[39m in `check_bool()`:
[22m
[1m
[22m
[33m!
[39m `nl1` should be either `TRUE` or `FALSE`
[31m✖
[39m `nl1` is
[34m<NULL>
[39m
check_bool(nl1, alt_null = TRUE) # returns invisible NULL
check_class
This function checks if class of the argument matches
the expected class. As an alternative you can allow the
argument to be NULL.
character_s <- "x"
character_v <- c("x", "1")
df_1 <- data.frame(x = 1:4)
check_class(character_s, arg_class = "logical")
[1m
[33mError
[39m in `check_class()`:
[22m
[1m
[22m
[33m!
[39m `character_s` should be of class
[34m<logical>
[39m
[31m✖
[39m `character_s` is
[34m<character>
[39m
check_class(character_v, arg_class = "numeric")
[1m
[33mError
[39m in `check_class()`:
[22m
[1m
[22m
[33m!
[39m `character_v` should be of class
[34m<numeric>
[39m
[31m✖
[39m `character_v` is
[34m<character>
[39m
check_class(df_1, arg_class = "data.frame")
check_color
Useful function for any plotting function that allows user to define colors. Additionally you can pass additional message.
wrong_color <- c("sausage", "green")
check_color(wrong_color,add_msg = "God I Love {wrong_colors}")
[1m
[33mError
[39m in `check_color()`:
[22m
[1m
[22m
[36mℹ
[39m God I Love sausage
[33m!
[39m `wrong_color` should contain only valid color names or color's hex values
[31m✖
[39m `wrong_color` contains 1 value that is not color:
[34m"sausage"
[39m
[36mℹ
[39m Use `colors()` to get the full list of color names
check_file and check_dir
check_file("xyz.Rmd", exists = TRUE, msg = "error")
[1m
[33mError
[39m in `check_file()`:
[22m
[1m
[22m
[33m!
[39m File
[34mxyz.Rmd
[39m does not exist. Aborting operation.
check_file("no_file_for_old.Men", exists = TRUE, msg = "error")
[1m
[33mError
[39m in `check_file()`:
[22m
[1m
[22m
[33m!
[39m File
[34mno_file_for_old.Men
[39m does not exist. Aborting operation.
check_dir("no_directory", create = FALSE)
[1m
[33mError
[39m in `check_dir()`:
[22m
[1m
[22m
[33m!
[39m Directory
[34mno_directory
[39m does not exist. Aborting operation.
check_length
Simply checks if the argument has expected length. Usually we want to check for length to be 1. But also might get handy in some plotting functions where we expect the length of colors vector to be same length as number of groups.
n1 <- 1.5
c3 <- c("a", "w", "z")
check_length(n1, arg_length = 2L)
[1m
[33mError
[39m in `check_length()`:
[22m
[1m
[22m
[33m!
[39m `n1` should be of length
[34m2
[39m
[31m✖
[39m `n1` is of length
[34m1
[39m
check_length(c3, arg_length = 1L)
[1m
[33mError
[39m in `check_length()`:
[22m
[1m
[22m
[33m!
[39m `c3` should be of length
[34m1
[39m
[31m✖
[39m `c3` is of length
[34m3
[39m
check_allowed_strings, check_exact_strings
or check_needed_strings
These three functions operate with character vectors.
The check_allowed_strings throws an error when the argument
contains any string that is outside of allowed strings. However it won’t
throw an error when the argument doesn’t contain all of the allowed
strings.
fail_allowed <- c("I", "don't", "like", "pvalues")
pass_allowed <- "pvalues"
check_allowed_strings(fail_allowed, allowed_strings = c("I", "like", "pvalues"))
[1m
[33mError
[39m in `check_allowed_strings()`:
[22m
[1m
[22m
[31m✖
[39m `fail_allowed` has the following unwanted string:
[34m"don't"
[39m.
check_allowed_strings(pass_allowed, allowed_strings = c("I", "like", "pvalues"))The check_exact_strings() function expects the whole
string vector to match the expected strings. This might be handy if we
pass a list with some elements, and we expect that it has exactly the
elements we need. Since it checks that two character vectors have the
same elements, it will also inform you about lacking strings as well as
about additional strings.
fail_list <- list(a = 1, b = 2, d = 4)
fail_list2 <- list(a = 1, b = 2, e = 4)
pass_list <- list(a = 4, b = 3, c = 2, d = 1)
check_exact_strings(names(fail_list), c("a", "b", "c", "d"))
[1m
[33mError
[39m in `check_exact_strings()`:
[22m
[1m
[22m
[31m✖
[39m `names(fail_list)` is missing the following string:
[34m"c"
[39m.
[32m✔
[39m `names(fail_list)` has no unwanted strings.
check_exact_strings(names(fail_list2), c("a", "b", "c", "d"))
[1m
[33mError
[39m in `check_exact_strings()`:
[22m
[1m
[22m
[31m✖
[39m `names(fail_list2)` is missing the following strings:
[34m"c"
[39m and
[34m"d"
[39m.
[31m✖
[39m `names(fail_list2)` has the following unwanted string:
[34m"e"
[39m.
check_exact_strings(names(pass_list), c("a", "b", "c", "d"))In the case of check_needed_strings() it is meant to
check if argument contains all the needed strings, but it does not care
about any additional strings. A good example when it comes handy is when
we want data.frame to have some specific columns (e.g. when
we expect response_id and weight), but are
flexible in the case of others.
fail_df <- data.frame(response_id = 1:3, m1_q1 = 2:4, m2_q1 = 5:7)
pass_df <- data.frame(response_id = 1:3, weight = rep(1.5,3), m5_q1 = 9:11)
check_needed_strings(names(fail_df), c("response_id", "weight"))
[1m
[33mError
[39m in `check_needed_strings()`:
[22m
[1m
[22m
[31m✖
[39m `names(fail_df)` is missing the following string:
[34m"weight"
[39m.
check_needed_strings(names(pass_df), c("response_id", "weight"))
check_inequality
This function checks if argument is greater or smaller than expected
value. By setting arg_equal = TRUE the test will pass if
the argument is equal to the expected value.
n1 <- 666.67
n2 <- -3.14
check_inequality(n1, arg_inequality = "greater", arg_value = 667)
[1m
[33mError
[39m in `check_inequality()`:
[22m
[1m
[22m
[33m!
[39m `n1` should be greater than
[34m667
[39m
[31m✖
[39m `n1` is
[34m666.67
[39m
check_inequality(n2, arg_inequality = "lower", arg_equal = TRUE, arg_value = -3.14)
check_scalar
First complex function check if argument is scalar. It means that it
will check if the argument is of proper class and it’s
length is exactly 1.
numeric_v <- c(2.43, 11.4)
numeric_s <- 3.14
check_scalar(numeric_v, arg_class = "numeric")
[1m
[33mError
[39m:
[22m
[1m
[22m
[36mℹ
[39m `numeric_v` should be
[34m<numeric>
[39m of length
[34m1
[39m
[33m!
[39m `numeric_v` should be of length
[34m1
[39m
[31m✖
[39m `numeric_v` is of length
[34m2
[39m
check_scalar(numeric_s, arg_class = "logical")
[1m
[33mError
[39m:
[22m
[1m
[22m
[36mℹ
[39m `numeric_s` should be
[34m<logical>
[39m of length
[34m1
[39m
[33m!
[39m `numeric_s` should be of class
[34m<logical>
[39m
[31m✖
[39m `numeric_s` is
[34m<numeric>
[39m
check_scalar_sign
The check_scalar_sign in addition to checking if
argument is a scalar, it will also check if it is positive or negative.
By setting arg_equal = TRUE it will rather check if the
scalar is non-positive or non-negative. It is also
possible to allow the argument to be NULL.
pos_s <- 44
neg_s <- -11
zero_s <- 0
nl_s <- NULL
check_scalar_sign(pos_s, arg_sign = "negative")
[1m
[33mError
[39m in `check_scalar_sign()`:
[22m
[1m
[22m
[33m!
[39m `pos_s` should be smaller than
[34m"0"
[39m
[31m✖
[39m `pos_s` is
[34m44
[39m
check_scalar_sign(neg_s, arg_sign = "positive")
[1m
[33mError
[39m in `check_scalar_sign()`:
[22m
[1m
[22m
[33m!
[39m `neg_s` should be greater than
[34m"0"
[39m
[31m✖
[39m `neg_s` is
[34m-11
[39m
check_scalar_sign(zero_s, arg_sign = "negative", arg_equal = TRUE)
check_scalar_sign(nl_s, arg_sign = "positive", alt_null = TRUE)
[1m
[33mError
[39m in `check_scalar_sign()`:
[22m
[1m
[22m
[33m!
[39m `nl_s` should be of length
[34m1
[39m
[31m✖
[39m `nl_s` is of length
[34m0
[39m
check_multiple
Allows to use same test over multiple variables. As soon as the check
fails, it will abort and throw error. It means that if more than one
argument is wrong, you will still need to work them one by one. This
function additionally requires user to pass the arguments wrapped into
check_arg function
(e.g. check_multiple(check_arg(arg1, arg2), check_function, arg_check_function = x)))
n1 <- 1
n2 <- c(1,2)
n3 <- c(1,2,3)
check_multiple(check_args(n1, n2, n3), check_length, arg_length = 1L)
[1m
[33mError
[39m:
[22m
[1m
[22m
[33m!
[39m `n2` should be of length
[34m1
[39m
[31m✖
[39m `n2` is of length
[34m2
[39m
n1 <- 1
n2 <- c(2)
n3 <- c(1,2,3)
check_multiple(check_args(n1, n2, n3), check_length, arg_length = 1L)
[1m
[33mError
[39m:
[22m
[1m
[22m
[33m!
[39m `n3` should be of length
[34m1
[39m
[31m✖
[39m `n3` is of length
[34m3
[39m
check_multiple(check_args(character_v, character_s), check_class, arg_class = "integer")
[1m
[33mError
[39m:
[22m
[1m
[22m
[33m!
[39m `character_v` should be of class
[34m<integer>
[39m
[31m✖
[39m `character_v` is
[34m<character>
[39m
check_strings
Is a wrapper over functions that check if character vector contains
allowed, exact or needed strings. It also needs you to wrap the argument
with check_args function.
check_strings(check_args(fail_allowed), expected_strings = c("don't"), type = "allowed")
[1m
[33mError
[39m:
[22m
[1m
[22m
[31m✖
[39m `fail_allowed` has the following unwanted strings:
[34m"I"
[39m,
[34m"like"
[39m, and
[34m"pvalues"
[39m.
check_strings(check_args(names(fail_list)), expected_strings = c("a", "b"), type = "exact")
[1m
[33mError
[39m:
[22m
[1m
[22m
[32m✔
[39m `names(fail_list)` has all the expected strings.
[31m✖
[39m `names(fail_list)` has the following unwanted string:
[34m"d"
[39m.
check_strings(check_args(names(fail_df)), expected_strings = c("response_id", "weight"), type = "needed")
[1m
[33mError
[39m:
[22m
[1m
[22m
[31m✖
[39m `names(fail_df)` is missing the following string:
[34m"weight"
[39m.
check_null_value
This function’s fate is still uncertain. It is meant to check if an
argument is NULL and if not if it has a certain value.
Since this is very limited scope, we might remove it from package to
avoid clutter.
nl1 <- NULL
c1 <- "a"
n1 <- 11L
check_null_value(x = nl1, arg_value = 11)
check_null_value(x = n1, arg_value = 11) #fails since we pass numeric value and it expects integer
[1m
[33mError
[39m in `check_null_value()`:
[22m
[1m
[22m
[33m!
[39m `n1` should be of value
[34m11
[39m or
[34m<NULL>
[39m
[31m✖
[39m `n1` is
[34m<11>
[39m
check_null_value(x = c1, arg_value = "a")