Automated Testing With Rust

theoforger

TheoForger

Posted on November 9, 2024

Automated Testing With Rust

This week, continuing my work on the Mastermind project, I implemented more automated testing! More specifically, I refactored my old testing code from integrated tests to unit tests, and finished implementing tests for other parts of the code.

Integration Test vs Unit Test

In the early stages I had implemented some testing. I even mentioned this in a previous blog. At the time I thought they were unit tests. After going through the doc, I learned they were actually integration tests, since I had them in a centralized separate module.

In the spirit of trying something different, I decided to migrate them to unit tests, where I keep the test code in the same place as the program code. It also gave me a change to compare these two approaches:

With my integration tests, my focus was mainly the input and output. I set up a mock server and ran all the necessary code to produce the final output. Finally, I used the assert_eq! macro to ensure the output was as expected.

Migrating to unit tests, I had to break the test code into pieces and focus on one function in each test unit. I also noticed myself naturally think about validating every step rather than just the input and output.

Correct me if I'm wrong, but here are my thoughts: Integration testing works better for a top-down approach where you want to ensure all the possible inputs produce the correct outputs without failure. Unit testing works better for a bottom-up approach where you want to ensure each component of the code is working correctly.

Mock API Server and Config Files

The most challenging part was the code related to API calling and config files, since they require some kind of external resources which I had to simulate for these tests.

For the mock API server, I used the httpmock crate. It allowed me to create an HTTP server for my testing environment. The server would simply serve a static file so the output is predictable.

For the config files, I used the 'tempfile' crate, or more specifically, the tempdir function to create a temporary directory. So that any file creation or modification can happen inside without affecting the real config files.

To Set or Not to Set

I did run into a dilemma regarding struct member visibility. To manage API calls, I had a struct like so:

pub struct Instance {
    client: reqwest::Client,
    base_url: String,
    api_key: String,
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the members are private. However, during testing I needed to change the base_url to my local mock server.

On the one hand, I have recently read a discussion that idiomatically, Rust prefers public struct members to "getters" and "setters", since these functions can get in the way of Rust's borrow checker.

On the other hand, is it really a good idea to make it a public member just because it's needed for a test?

I ended up going with a setter, with a #[cfg(test)] annotation so that it's only compiled for tests, like so:

#[cfg(test)]
impl Instance {
    pub(crate) fn set_base_url(&mut self, base_url: String) {
        self.base_url = base_url;
    }
}
Enter fullscreen mode Exit fullscreen mode

Still, this doesn't feel quite right. I wonder if there's a better solution out there?

💖 💪 🙅 🚩
theoforger
TheoForger

Posted on November 9, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Hurl, I've Come to Bargain
opensource Hurl, I've Come to Bargain

November 25, 2024

Automated Testing With Rust
opensource Automated Testing With Rust

November 9, 2024

What If I Told You I Created a Mastermind?
opensource What If I Told You I Created a Mastermind?

September 21, 2024

First Day on My First Project
opensource First Day on My First Project

September 10, 2024