Try   HackMD

How to mock the struct in the Rust

This article serves as a record of my recent work. If you notice any errors or have any feedback, please leave a message.

It's important to note that the 3rd party function codes mentioned, such as disk_info or remaining_time, are provided as examples and may not guarantee correctness or compilability. These functions are intended to demonstrate certain concepts or serve as starting points for further development. It's essential to review and adapt them according to the specific requirements and environment of your project.

Hows it looks like at first?

The Structure

This structure is aims to provide the disk information and the battery information.

struct SystemInformation{ used_disk: u32, available_disk: u32, battery_remaining_time: Option<u64> }

How can I retrieve these information?

There are two crates able to get these information cross platform. sys_info used to retrieve the disk information and battery used to retrieve the battery information.

impl SystemInformation{ pub fn new() -> Self{ let diskinfo: sys_info::DiskInfo = sys_info::disk_info(); let used_disk = diskinfo.total - diskinfo.free; let free_disk = diskinfo.free; let battery_remaining_time: Option<u64> = battery::remaining_time(); Self{ used_disk, free_disk, battery_remaining_time, } } }

What is the problem?

I am unable to isolate the dependencies.

Isolate the dependencies

How about use the trait to isolate it?

pub trait InformationGetter{ fn get_disk_info() -> sys_info::DiskInfo; fn get_remaining_time() -> Option<u64>; }

Implement it for my struct

impl InformationGetter for SystemInformation{ fn get_disk_info() -> sys_info::DiskInfo{ sys_info::disk_info() } fn get_remaining_time() -> Option<u64>{ battery::remaining_time() } }

Lets change the calls in new function

impl SystemInformation{ pub fn new() -> Self{ let diskinfo: sys_info::DiskInfo = Self::get_disk_info(); let used_disk = diskinfo.total - diskinfo.free; let free_disk = diskinfo.free; let battery_remaining_time: Option<u64> = Self::get_remaining_time(); Self{ used_disk, free_disk, battery_remaining_time, } } }

The code is work and the 3rd party function calls are isolated! Lets use the mockall

Mockall

Accroding to the [document|https://docs.rs/mockall/latest/mockall/], I only have to add #[automock] on the trait right?

#[automock] pub trait InformationGetter{ fn get_disk_info() -> sys_info::DiskInfo; fn get_remaining_time() -> Option<u64>; }

No! The function that you gonna mock should have the parameter self.
Lets implement it.

#[automock] pub trait InformationGetter{ fn get_disk_info(&self) -> sys_info::DiskInfo; fn get_remaining_time(&self) -> Option<u64>; } impl InformationGetter for SystemInformation{ fn get_disk_info(&self) -> sys_info::DiskInfo{ sys_info::disk_info() } fn get_remaining_time(&self) -> Option<u64>{ battery::remaining_time() } }

It seems great right? However, there is some error occur! There is no parameter we can pass into the function when we call it.

let diskinfo: sys_info::DiskInfo = Self::get_disk_info(); let battery_remaining_time: Option<u64> = Self::get_remaining_time();

What about this pub fn new(self)? It seems strange to pass self as a parameter to create an instance of itself.

Think outside of the box

Considering the trait is named "Getter," we can view it as a tool or instrument that allows us to retrieve information. In this case, we can design the function to accept the tong (the instance implementing the Getter trait) as a parameter. By doing so, the function caller can provide us with the necessary tool to extract the desired information.

pub struct DefaultTong; impl InformationGetter for DefaultTong{ fn get_disk_info(&self) -> sys_info::DiskInfo{ sys_info::disk_info() } fn get_remaining_time(&self) -> Option<u64>{ battery::remaining_time() } }

Now that we have the tong, it's important to consider that users may not be aware of the internal structure or requirements of our system. They might not know that they need to pass a tong to our function.

To address this, we can provide a more user-friendly interface by encapsulating the usage of the tong within our function. Instead of expecting users to pass a tong explicitly, we can abstract away the implementation details and provide a simpler and more intuitive API.

By designing our function to hide the complexity and handle the retrieval of information internally, users won't need to explicitly pass a tong. This approach ensures a smoother and more user-friendly experience, allowing users to interact with our system without needing knowledge of its internal workings.

impl SystemInformation{ pub fn new() -> Self{ let tong = DefaultTong; Self::new_with_tong(tong) } pub fn new_with_tong<T: InformationGetter>(tong: T) -> Self{ let diskinfo: sys_info::DiskInfo = tong.get_disk_info(); let used_disk = diskinfo.total - diskinfo.free; let free_disk = diskinfo.free; let battery_remaining_time: Option<u64> = tong.get_remaining_time(); Self{ used_disk, free_disk, battery_remaining_time, } } }

Finally, Mockall

To effectively test our implementation, we can utilize unit testing and employ mocks to manipulate the return values. It's crucial to note that when writing unit tests for a trait, we should implement the return values for each function defined within the trait.

By creating mock implementations for the trait's functions, we can control and manipulate the behavior of these functions during testing. This enables us to simulate different scenarios and ensure that our code behaves as expected in various conditions.

It's essential to provide thorough test coverage by implementing the necessary return values for each function in the trait. This approach guarantees that our code is rigorously tested and can handle different scenarios and edge cases effectively.

#[cfg(test)] mod tests{ use super::*; use mockall::*; use mockall::predicate::*; #[test] fn mock_remaining_battery_time() { let mut mock_tong = MockInformationGetter::new(); mock_tong.expect_get_disk_info().returning(move || sys_info::DiskInfo{total: 1024, free: 128}); mock_tong.expect_get_remaining_time().returning(move || Some(60)); let result = SystemInformation::new_with_tong(mock_tong); assert_eq!(Some(60), result.battery_remaining_time); } }