Outline
This is from a tweet which you can find here.
Do you know what the difference between interaction and state-based unit testing is and which is better? (do you know which one is used on stateless code?)
quick rundown here: 👇
Briefly, interaction testing, is testing how your unit/piece of code (called the System Under Test or SUT) interacts with another unit/piece of code.
State-based testing (aka testing state) tests that your SUT either returns the right result, or modifies state correctly.
State-based testing is always preferred. The primary reason is that it is less coupled to your code. So you can more easily change your code without changing the test.
For example, a test that checks you calculated a Fibonacci sequence correctly doesn't have to care HOW you calculate it. So that if you refactor the code to be faster or more readable, the test wouldn't change.
let sequence = myFib.generateSequence(5)
expect(sequence).toEqual([0,1,1,2,3])
Another example is testing that an item gets added to the cart without caring HOW it gets added. You can also call this "the ends justifies the means" testing...
cart.add(newItem);
expect(cart.items[0]).toBe(newItem)
Interaction testing on the other hand tests how your code interacts with other code. A good example is checking that you tell the server about changes to local state, such as adding an item to the cart. This is usually done with some kind of mocking library
let cart = new Cart(mockServer)
cart.add(newItem);
expect(mockServer.updateCart).toHaveBeenCalled()
In this case, the interior code could change some, but if we used a different method to update the server, then the test breaks, but the algorithm may still be working properly. If the test is more ingrained in the code, this gets exacerbated.
let cart = new Cart(mockServer)
cart.add(newItem);
expect(mockServer.updateCart)
.toHaveBeenCalledWith(newItem)
In the above code, we assume that updateCart is called and passed in the newItem object. But what if we refactored updateCart to receive just the id and quantity of the new item instead of an object? The test is now broken, although our code is still "working".
For this reason we may prefer state-based testing, but this may not always be possible when using mocks to isolate from external APIs, such as your HTTP calls.
One technique is to use a Fake. (see my previous thread on mock types for information on what a Fake is). https://twitter.com/josepheames/status/1292908852275363840
Here's an example where a fake is used instead of the real HTTP library and now we can query the fake in a state-based manner instead of caring about the interactions.
let cart = new Cart(fakeServer)
cart.add(newItem);
expect(fakeServer.cart.items[0])
.toBe(newItem)
However this technique has some major drawbacks. One is that you now need to keep your fakes in sync with the production library and how you use the production libary. If this is a 3rd party library, it may have a huge API surface that you don't wish to keep up with.
Also, even though fakes are short-circuited they can still get complex. And that greatly increases the possibility of a bug in your fake. So now you end up in the unenviable position of having to test your tests.
Who watches the watchers?
A more functional approach to code can help ameliorate this issue. If you are ready to get your mind bent with a really esoteric discussion of this, see this amazing article by Eric Elliot. https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a
Those are the two types of tests. Preferring state-based when possible is good practice, and you will find that there are times when you could check the correctness of an algorithm through either method. In those cases, test the state.
Discounted Courses
Here at Thinkster, we use educational science to teach you five times faster than you would learn the same topics elsewhere. By joining as a Pro subscriber you have access to our complete library on courses on Angular, React, Vue, JavaScript, and More.
To help you get started, use this link for a discount on a monthly membership.
Or save even more with an annual membership.