How to test private impl static method in separate file

Hello there, I have this situation:

Two modules, device.rs and tests.rs. So in my main.rs, I have:

mod device;

#[cfg(test)]
mod tests;

fn main() {
  // ...
}

In device.rs, I have a Device struct with its impl:

pub struct Device {
    // ...
}

impl Device {
  fn virt_to_phys_addr(mut virt_addr: u32) -> PhysAddr {
    // ...
  }
}

I want to test virt_to_phys_addr() in impl Device, so in my tests.rs, I have:

use crate::device::Device;

#[test]
fn virt_to_phys_addr() {
    assert_eq!(2 + 2, 4);
    Device::virt_to_phys_addr(0x80000000);
}

But I have this error:

error[E0624]: associated function `virt_to_phys_addr` is private
 --> src/tests.rs:6:13
  |
6 |     Device::virt_to_phys_addr(0x80000000);
  |             ^^^^^^^^^^^^^^^^^ private associated function

I don't want to mark Device::virt_to_phys_addr() pub just for the test, I don't want to move it outside of Device impl.

My finds on Google tell "don't test private functions" or "you should not care about implementation details", which does not help.

So how can I test private static method only changing tho content of tests.rs.

Thanks in advance for the help all! Have a nice day! :slight_smile:

How about moving that test into the device module or to a device-specific device::tests module?

Thanks for the answer. I want to keep my tests outside of the device.rs file.

I don't really understand the second part of your answer. This doesn't work either:

//tests.rs
mod device {
    #[cfg(test)]
    mod tests {

        #[test]
        fn virt_to_phys_addr() {
            use crate::Device;
            assert_eq!(2 + 2, 4);
            Device::virt_to_phys_addr(0x80000000);
        }
    }
}
error[E0624]: associated function `virt_to_phys_addr` is private
 --> src/tests.rs:9:21
  |
9 |             Device::virt_to_phys_addr(0x80000000);
  |                     ^^^^^^^^^^^^^^^^^ private associated function

I'd be interested what others have to say. My approach so far has has been:

  • always implement unit tests as a sub-module since sub-modules can see parent's private fields
  • for integration and doctests instead of having private fields I'd use pub(crate) or
  • if the above doesn't fit the purpose, I use some helper functions that are enabled for tests only (using #[cfg(test)], although this didn't work for doc tests -- not sure whether it does now or not.)
1 Like

I was referring to putting a submodule inside of the already existing crate::device module. In case you want to avoid the tests cluttering the device module. IIRC that’s how (at least some of the) tests (even of public API) of types in the standard library are structured.

Thanks for the answers.

My problem is about file management. I don't want my tests to be in the device.rs file. Can I put them in a device_tests.rs file and have a device::tests module in it? How can I do that?

Note that having something inside tests.rs already places them in a tests module, so your test function is actually in the module tests::device::tests

You actually want to create a folder named device in the same folder of device.rs and place tests.rs inside it. Then in device.rs write mod tests;

File hierarchy - Rust By Example

You’ll have a choice between keeping device.rs as is or moving its contents to device/mod.rs, whichever you prefer. The first version has both a directory device and a file device.rs in scr. The (device-module specific) test can go into device/test.rs and you need the mod declaration, as @SkiFire13 already explained. Inside of this test module you can refer to Device by either crate::device::Device, or super::Device (both for direct use or in use statements).

Make sense, thanks.

Thanks. Maybe it's just me, but having to modify the file system to add tests looks highly restrictive to me. :confused:

Is there a way to have a foo.rs file with its own module organization inside? Like:

// foo.rs
mod device {
  impl Device {
    #[cfg(test)]
    mod tests {
    }
  }
}

What I'm trying to have here is a device::tests module inside a foo.rs file. Can I do that?

Thanks in advance! :slight_smile:

Thanks for the answer.

The problem is I have one or two tests per .rs files. This would mean I have to have one folder per .rs file. I prefer to have all those tests in one single file and test all modules in them.

Can't I separate the module concept from the file hierarchy?

There’s the path attribute. Though this cannot help you getting all the tests in a single file. In that case, you could consider the last option persented by @qaopm

Maybe a bit annoying, but I don't think it's actually restrictive.

As a start, the module tests can't be declared inside the impl Device {, although this is probably a typo. I'll take it as if you intended it to be outside the impl Device { but inside the mod device {.

Apart from that, here you're actually declaring a module foo::device::tests because they're declared inside the foo.rs file. There's no way to declare an higher-level module from inside a module.

Now that you make me think about it, if op just wants to have its unit tests grouped together it could have them in a tests folder but in separated files and use #[path = "tests/bar.rs"] mod tests; in each module.

1 Like

Side note:

Not to suggest that this is in any form a useful idea (it isn’t. Visibility does not depend on impl blocks but is entierly a concept related to modules), but IIRC you can even declare external modules inside of impl blocks if you give them a #[path = "..."].

Edit: No, wait, sorry.. that’s not inside of impl blocks but inside of function definitions (which can themselves be inside of impl blocks)... I got that mixed up.

HOLY!!!! It works !!! :open_mouth:

So the solution is multiple:

I can have a device_tests.rs and put tests in it:

// device_tests.rs
use super::*;

#[test]
fn virt_to_phys_addr() {
    let phys_addr = Device::virt_to_phys_addr(0x80000000);
    assert_eq!(phys_addr.addr, 0);
}
// device.rs

#[path = "tests.rs"]
#[cfg(test)]
mod tests;

I suspect the idea of tests folder is even better but the idea is here: Put a mod tests inside another module without relying on hierarchical manipulation on the file system.

I was so devastated. Thanks you all for the solution!

1 Like