/images/avatar.jpg

Jorge Ortiz-Fuentes' DevBites

Rust unit testing: basic HTTP testing

In previous articles in this series, I have highlighted the importance of unit testing your code. However, I must reckon that most of the examples I have used were mostly isolated. There weren't many connections between one scenario and the next, and, admittedly, not a single application that integrated all the scenarios as a whole.

I hope this has fulfilled the purpose of sharing knowledge about unit testing in Rust. But one of the first challenges I had to overcome once I had learned the very basics of writing automated tests was integrating them into a real codebase. But it is hard to walk this path alone, since the first (real) tests are also the hardest.

Rust unit testing: file writing

This is the third and last article on file I/O testing, and yet again, the focus is on the technique for injecting a dependency so we can use it in the tests. In this article, I will cover a writing scenario that requires us to access the test double after invoking our code to check the written contents.

Writing to a file

If you are just a regular villain, your henchmen are close to you. You have recruited them personally, know what they can do, and call each one of them by their name… Well, not really, because you don't care about their names. But you don't have any issues letting them know what you want from them, because they are always around you. Those times are long gone and deeply missed.

Rust unit testing: buffered file reading

In the previous article, I explained how to use a test double to replace a File instance and control the behavior required by a function that reads from it. With the test double in place, I was able not only to return what I needed for the scenarios where I needed reading, but also to produce errors that allowed me to test the not-so-happy path.

I got some concerned comments pointing out that the test code wasn't reading, which was an essential part of what we were testing. I agree that the test code isn't actually reading. It isn't, and that is on purpose. On the one hand, reading isn't an essential part of what we are testing. On the contrary, File is a dependency of the are_there_vulnerable_locations() method, and, as with any other dependency, it can be replaced by a test double. The code isn't making it easy to inject the dependency, because the File instance is created inside of the method –and as you may already know, every time you create an instance inside of your code, a kitten is killed2. But I applied what I had already explained to replace dependencies using the namespace. On the other hand, we gain additional benefits by using a test double. I mentioned that performance is much better, but you can only notice the difference with a large test suite. However, it is also important that the test double can easily reproduce File behaviors that would require some work when you are actually interacting with the filesystem. For example, opening the file for reading successfully and then failing to read from it right away.

Rust unit testing: file reading

So far, I have covered several scenarios. Yet all the interactions in those scenarios occurred in memory: one instance of a type talking to another instance of another type. However, in the real world, our applications do more than just invoke functions or methods, and we would like to test those use cases.

One of the most common things that applications do and that deserves testing is reading from and writing to files. Files are among the most frequently used input and output mechanisms in many applications, and it is key that they behave as expected when using files.

Rust unit testing: mocking library

In earlier articles about test doubles, I showed how to create them without using any libraries. While I believe that is the best way to understand how they work, how they can help, and how to use them, it is far from being a convenient way of writing your tests.

In this article, I will explain how to use a mocking library and replace all the test doubles we wrote with others that are produced by the library. Prepare for a more streamlined version of the code that achieves the same goals with less effort!

Rust unit testing: assertion libraries

At the beginning of this series, I explained how to write a test by decomposing it into three parts: arrange, act, and assert. Since then, we have written several unit tests using that structure. Most of the assertions we've written in those tests used the assert_eq!() macro. Sometimes, they were a very obvious way to state what was expected and the actual value that was produced. It’s clear and works well for simple checks. However, in other cases, the syntax is awkward or doesn't clearly convey what we mean, requiring extra steps or missing necessary functionality.