Manish Shivanandhan Machine learning, cybersecurity, & AWS professional. Writes at hardcoder.io.

A guide to mocking in Rust using Mockall

5 min read 1581

Guide To Mocking Mockall

Testing is an integral part of software engineering. For beginners, writing a test case makes sure that your code does exactly what you expect it to do. Every programming language has various frameworks that help you test your code.

Small pet projects can get away with not having testing in place, but as an application scales, you run into the risk of hitting a wall where you become paranoid after you push a new feature to production.

Some teams use a manual tester who performs regression testing. This is great in theory, but a manual tester cannot capture all the intricacies that arise during runtime. And, given the tools available for automated testing, it is expensive and unproductive to use a manual tester.

In spite of this, the percentage of engineers who prefer to test their code is very small; but if you look at the best engineering teams that build high-quality software, testing will be an integral part of their workflow.

In this article, we will deep dive into mocking Rust. We will understand how mocking differs from general unit testing and how to implement mocking using the Mockall library.

This article assumes some familiarity with Rust. If you are brand new to Rust, you can read an introduction to Rust here.

What is unit testing?

Now that you know the importance of testing your code, let’s look at how unit testing works. You will understand the need for mocking when you understand how unit testing works.

Let’s assume you have a function that takes in two numbers and returns the division of those two numbers.

function divide (a,b){
        return a / b
}

Pretty simple function. You provide two numbers, and you get an output; but, the question is, is that always the case?

What if b is 0? It produces Zero Division Error in most languages, since anything divided by zero is infinity.


More great articles from LogRocket:


What if a and b are arrays? Can you be sure that the code calling your function will only pass the expected data types? Unfortunately, you cant.

This is where unit testing comes in. Unit testing tests your code in many ways to make sure your code can handle these types of anomalies.

Well, it doesn’t do that automatically — you have to write those test cases yourself.

For example, to test the division function, you write some test cases as in the following:

expect divide(2,2) to be 1

expect divide (1,0) to throw an error

Now you know why developers usually don’t like to write test cases. It’s a lot of work, but once you get used to it, the benefits justify the effort.

What is Mocking?

So, unit testing sounds straightforward; now, what exactly is mocking?

The example division function was a simple one. Real-world functions can be quite complicated. Also, there can be many dependencies that functions use.

For example, a function to convert .pdf to text might use an external dependency like pdf-extract. To test these complicated functions, you will have to include all the external dependencies in your test cases.

That just complicates things way too much. What if there was a simpler way? There is and it’s called “mocking.”

An object/function that is being tested may have dependencies on other objects. To isolate the behavior of the function that you are trying to test, you can replace the object by using mocks. Simply put, a “mock” can simulate the behavior of complicated functions without the need to include them.

This is often useful when the real function/object is hard to include in a test case. For example, to test a few database operations, you can use a simple in-memory store to read and write data.

This will simulate the operations of a database without the need to set up an actual database to test its functions. This is the core idea behind mocking.

Here is an excellent article on how mocking works in general. Now, let’s look at how Rust helps us to do mocking.

Methods of mocking in Rust

Rust has a wide range of mocking libraries that work in different ways. Every mocking library has its own set of features, pros, and cons. In this article, we will be looking at the Mockall library.

Mockall

Though there are many mocking libraries available for Rust, Mockall is the most powerful mocking library available as of now. It combines the benefits of many other mocking libraries. It also has a user-friendly interface. It does not use any unsafe code and runs on stable Rust. Moreover, Mockall contains tools to develop mock versions of structs or traits.

How does Mockall work?

Before we dive into writing mocks, it’s important that you understand the difference between structs and traits. Simply put, structs and traits are similar to classes and properties in most languages. Here is a great article to learn more about structs, traits, and impl blocks.

The following are the two main methods for using Mockall:

  1. #[automock], to mock traits or structs that only have an impl block
  2. mock!, for the remaining things that are not covered in the first method

Let’s write some code. First, let’s write a simple example to mock a trait and expect it to return what we are looking for.

#[automock]
trait MyNewTrait {
    fn func1(&self) -> u32;
    fn func2(&self, x: u32, y: u32) -> u32;
}

let mut mock = MockMyNewTrait::new();
mock.expect_func1()
    .return_const(42u32);
mock.expect_func2()
    .returning(|x, y| x + y);

The above example shows how we can mock a simple trait using the #[automock] definition.

Now, let’s mock a struct. Since #[automock] only works for structs with a single impl block, we have to use the !mock definition. We will write a mock struct that implements multiple traits.

pub trait Trait1 {
    fn func1(&self);
}

pub trait Trait2: Trait1 {
    fn func2(&self);
}

mock! {
    Trait3 {}
    impl Trait1 for Trait3 {
        fn func1(&self);
    }
    impl Trait2 for Trait3 {
        fn func2(&self);
    }
}
let mut mock = MockTrait3::new();
mock.expect_func1().returning(|| ());
mock.func2().returning(|| ());
mock.func1();
mock.func2();

In the above example, we have two traits followed by a mock struct that implements the two traits. This uses the !mock macro since there are more than one implementation.

These are the two ways of using Mockall. In addition to mocking structs and traits, you can also mock entire modules using Mockall library.

Alternatives for mocking in Rust

In addition to Mockall, there are other options for mocking in Rust. Let’s skim through a few of them.

Mockers

Mockers is the oldest mocking library for Rust and was inspired by GoogleMock. Mockers have numerous helper methods, along with an efficient syntax, which is still used on stable Rust. However, nightly support is required to handle generic functions.

Mock Derive

Mock Derive was the first mocking library that introduced the concept of ‘derive’ing’ the mock object from the target trait. It is useful in simplifying the entire mocking process.

Mock Derive is known for its user-friendliness. However, its major disadvantage is that it is not capable of validating method arguments. Moreover, you cannot use Mock Derive with generic traits, traits with generic methods, or multiple traits. It is also not actively developed compared to the other available libraries.

Galvanic mock

Galvanic-mock is part of the testing libraries that work with galvanic-test and galvanic-asset. It provides thorough testing functionalities for Rust programs.

The salient feature of Galvanic-mock is that it distinguishes between the actual function of a mock and how that mock is expected to be utilized.

Pseudo

Mocking libraries that need the nightly compiler provides benefits like the capability of tweaking the language’s syntax. However, such libraries are inherently unstable. It cannot be guaranteed that the code that runs nightly will also work with future compilers.

Pseudo eliminates this issue. It removes the nightly-dependent features like deriving from making sure it can work on stable Rust. It’s worth noting that Pseudo is quite complicated to understand and use, particularly for beginners.

Mock-it

Mock-it is another mocking library available in Rust. It is becoming popular due to its simplicity. The primary benefit of using Mock-it is that it can be run on stable Rust while most mocking libraries in Rust are experimental using code generation. On the downside, Mock-it does not have a high-level API. Hence, its practical applications in large-scale mocking are highly limited.

Mocktopus

Mocktopus stands out from the long list of mocking libraries for Rust. It focuses on free functions instead of focusing on mocking traits. There are three main benefits of using Mocktopus.

Firstly, it has a low barrier to entry skills-wise. Secondly, it can deal with generic functions. And, thirdly, it can mock both structs and free functions.

Conclusion

Mocking is a core component of software testing. In addition to unit tests, mocking allows you to simulate the behavior of complicated components while keeping your test cases simpler to write and execute. Mockall is a fantastic library that provides all the required tools for mocking in Rust.

A major reason behind the continuously rising popularity of Mockall is that it works for structs, traits, and functions. Mockall is establishing itself as a go-to solution for mocking in Rust. It also comes with detailed documentation with examples to help you quickly get started with mocking in Rust.

LogRocket: Full visibility into production Rust apps

Debugging Rust applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Rust app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Modernize how you debug your Rust apps — .

Manish Shivanandhan Machine learning, cybersecurity, & AWS professional. Writes at hardcoder.io.

Leave a Reply