Often, we end up creating multiple unit tests for the same unit of code to make sure it behaves as expected with varied input. This is a good practice, but it can be a little tedious to create what is essentially the same test multiple times. We copy a test, paste it and update the variables that matter. Maybe we do that several times. Then, if we need to update our tests, we update each copy of the test. The good news is, starting with version 23
of Jest, there is built-in support for creating data-driven tests. In this lesson we'll take a handful of unit tests and reduce them down to a single, data-driven test with the new test.each
method in Jest. We'll also look at the alternate version of test.each
that lets us use a tagged template literal to describe the data for our test.
Additional info:
In the Jest docs: https://facebook.github.io/jest/docs/en/api.html#testeachtable-name-fn
Jest cheatsheet: https://github.com/sapegin/jest-cheat-sheet#data-driven-tests-jest-23
Instructor: [00:00] I'm starting off in the file that exports a utility function called isPalindrome. It takes a string and returns a Boolean indicating if the input string matches the reverse version of that string. It normalizes the strings by converting them to lower case, making the check case-insensitive. I also had some test defined that check the isPalindrome function against a few different inputs.
[00:20] These tests are essentially the same, but the only variation being their titles, the input, and the expected result. In each case, the title describes the test in terms of the values being used in the test. The input value is defined, passed to the isPalindrome function.
[00:35] We assert that the result is the Boolean value we expect. As we can see in the terminal, these tests are passing.
[00:42] Let's see how we can refactor this into data-driven tests using Jest test.each feature. We'll keep the existing test for comparison. I'll just collapse this to get it out of the way.
[00:53] Now, I'm going to drop down. I'm going to add a new describe. I'll give that a description, and we're still testing isPalindrome.
[01:01] I'm just going to call out the fact that we're doing this using the test.each feature. Then we'll pass in our function. I'm going to write test.each.
[01:15] Test.each is going to take a nested array, so we're going to pass in an array. Each element in this array is going to be an inner array.
[01:22] Each of these inner arrays is going to represent the data that we need for single one of these test invocations. If we look in the terminal, we're basically testing isPalindrome with three different strings. Each one has a Boolean result. Those are the values that we need for our test.
[01:39] Each one of these arrays is going to have its first element as the string that we want to test as an input, so we'll do racecar, and then the expected result, which in this case is true. Then we'll have two more of these.
[01:53] The second word we're testing is typewriter. That is not a palindrome, so we expect the result of that to be false.
[02:02] Then we're also testing rotor, which is a palindrome. Now, we defined the data for three tests against this function. The result of calling test.each is a new function. We can come down here and we can add some parentheses to invoke the returned function. This is going to take two arguments.
[02:19] The first argument is a string. This string represents the description for our test that we see in the output and the terminal. We'll do the same thing. We're testing isPalindrome. Then we're showing what that input is going to be and then what we expect the result to be.
[02:37] The way this works is we're going to throw in a %s, and this is going to represent the first item in the array for this invocation of the test. Then we can put in another %s, and that will represent the second item.
[02:53] Now that we have our name defined, the second argument to this returned function is a function. This is going to be our actual test code.
[02:59] Here, we're going to have a function. It's going to accept arguments. Each one of the elements in these inner arrays is going to be one of the arguments. We have our string which is our input, and then we have our Boolean which is out expected result. Then in here, we need to make our assertions for our test.
[03:19] I'm going to create a constant. We'll call it result. That's going to be a call to isPalindrome with the input for this test invocation.
[03:28] Then I'm going to use expect. I'm going to expect result to be the expected value. Now we've defined our data, we have descriptions for our test, and we have code that will take those individual inputs and make our assertions.
[03:48] Let's save this file. We'll see our test run. We'll see in the terminal that now we have a second set of tests that all match and everything is passing. We've been able to take these three original tests, and describe them with some data and some shared code.
[04:07] Now, if I want to add an additional check to my test, all I have to do is update this array and add another inner array. Let's say I want to test the word "kayak," and I expect to be true. We'll save that.
[04:22] Test will run again. By simply adding this extra array, we've gotten a fourth test. I can take that another step, and I can test a phrase.
[04:34] We'll say, "Step on no pets," solid advice, also a palindrome. I expect my output to be true. Save.
[04:43] Now, I have five conditions being checked without having to update my test code. Test.each gives us a second way to describe our data using a tagged template literal. Let's see how that works.
[04:55] I'm going to collapse this describe. I'm going to drop down or I'm going to add a new one. We're also testing isPalindrome with each, this time using a tag template literal. I'll just throw in my function here.
[05:15] Then, like we did before, we'll call test.each. This time, instead of using parens to pass in arguments, I'm going to use backticks.
[05:23] I'm going to pass this template literal. This template literal is essentially going to be a table of data that describes our test. We're going to start our table off with a header. The header is going to have columns that basically name our variables for us.
[05:42] We're going to have our input which will represent our string, and our expected value which will be our Boolean. Then each row in this table is going to represent the data for each test invocation.
[05:53] We use interpolation to represent our values. That's going to be the dollar and curly braces. Then my values are going to go inside here.
[06:01] I'll do my string as my input, pipe to separate my columns, and then I'll also interpolate my expected value, in this case will be true. Then I'm going to create two more rows. We're also going to test typewriter which we expect to be false, and rotor which we expect to be true.
[06:24] Just like the other version of each, this is going to yield a function that we can invoke with parentheses. The first argument is going to be a string that takes our test description. I'll do that just like we did before. Except this time, instead of using these positional arguments that we did before with the %s, we can reference our variables by name.
[06:46] We just have to prefix some with the dollar signs. We can do $input, because that will match this first value. Over here, we can do $expected to match our second value.
[06:59] Now, the second argument to this function is going to be the function that runs our expectation. We'll create a function here. This is going to accept an object as an argument. That object is going to represent our row of data.
[07:14] For now, I'm just going to call this row. Then down here, I'm going to declare a constant called result. I'm going to set that to equal called the isPalindrome. That's going to accept row.input as an argument. Then we'll add our expectation with expect, and we expect result to be row.expected.
[07:40] Now we can save this. Our test will run again. Now we have a new set of tests that match the previous set of tests, and everything is passing.
[07:50] We can come back in here. We can refactor this a little bit. We get this object.
[07:54] We know what our values are going to be, so we can destructure this or we can just pull out input and expected rather than defining row and then using row.. Now, we can come down here and just reference input and expected directly. We can save that, make sure everything still passes. We're still good.
[08:13] The other thing we can do up here is we can actually line these up so that this is a little bit more readable. We'll save that. That will still work as expected, and all of our tests continue to pass. Now we can drive our test with data either using a nested array or a template literal to represent our table of data.