A small gotcha when comparing lists using testthat

(Update: Some commenters have rightly pointed out that the crux of the issue I faced was the difference in selecting elements from a list using $ vs. [[. Nevertheless, I thought I would leave the problem I faced in its original context since (i) the error doesn’t present itself right away, and (ii) the best solution doesn’t involve replacing $ with [[.)

I recently encountered a small gotcha when using the testthat package to test the equality of two lists. Imagine I have the following two lists below:

list1 <- list(a = 1, b = 2, c = 3, d = 4)
list2 <- list(a = 0, b = 2, c = 3, d = 5)

Imagine that:

  1. I know that the value associated with a is going to be different, so I don’t want to test equality for it.
  2. The values associated with the other keys should be the same, so I want to test equality for them.

If I run the tests correctly, I should get an error message associated with the key d. The following code snippet using the testthat package looks like it does the job, BUT IT DOES NOT WORK:

library(testthat)

# WRONG!!!!
for (key in names(list1)) {
    if (key != "a") {
        expect_equal(list1$key, list2$key)
    }
}

The code above incorrectly reports that all the elements in list1 and list2 (ignoring the key a) are equal.

The correct way to test equality in this situation is as follows:

library(testthat)

# CORRECT
keys <- setdiff(names(list1), "a")
expect_equal(list1[keys], list2[keys])
# Error: list1[keys] not equal to list2[keys].
# Component ā€œdā€: Mean relative difference: 0.25

Update: As some of the commenters have rightly pointed out (thanks for contributing!), the bigger mistake in the first snippet is the use of $ to pull out the element instead of the double square braces [[. When using $, it is looking for a key in the list that is literally named "key". Not what I intended!

The code snippet above can be changed to use [[, but notice how the error message is less informative:

library(testthat)

# CORRECT, but error message less informative
for (key in names(list1)) {
    if (key != "a") {
        expect_equal(list1[[key]], list2[[key]])
    }
}
# Error: list1[[key]] not equal to list2[[key]].
# 1/1 mismatches
# [1] 4 - 5 == -1

2 thoughts on “A small gotcha when comparing lists using testthat

  1. Well, I do agree the second way is the better way, but your loop approach doesn’t work only because of the incorrect list subsetting through $.

    Currently you only really do this:
    list1$key == list2$key == NULL

    This code, however, will work:

    for (key in names(list1)) {
    if (key != “a”) {
    testthat::expect_equal(list1[[key]], list2[[key]])
    }
    }

    Like

  2. Hi There!

    When trying to subset and select an element of a list programmatically like that, you should be using double square braces, `[[`, instead of `$`. Using a dollar sign, it will look for the element with the name “key” in the list literally, not the string you want to find. If you switch to using `expect_equal(list1[[key]],list2[[key]])`, you should get the answer you are looking for šŸ™‚

    The dollar sign notation can be a huge gotcha, so when I am doing loops with lists, I try to only use the square braces!

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s