Ray Henry
Ray Henry

Reputation: 905

Unit test that can assert the visibility of a method (rust)

I would like to write a unit test that asserts that a method is private. The test has visibility to the method and can execute and test it.

The additional test I want is in order to confirm that the method remains private, i.e. I want to be able to fail a test if a future coder (or myself) "accidentally" changes the visibility to pub. Making the method public would break the design because it would allow clients of the implementation to change the struct in a way that would violate assumptions made in the rest of the design.

Is there some type of introspection or reflection that will allow this?

Upvotes: 1

Views: 381

Answers (1)

cameron1024
cameron1024

Reputation: 10136

As pointed out in the comments, field/function visibility is a concept that only exists at compile time, so you will need to find a way to test that some code fails to compile.

Doctests are a obvious tool, but are somewhat of a sledgehammer solution. I'd recommend the trybuild crate instead. I've mostly seen it used by macro authors, but it can generally be useful whenever you want to test that something either does or doesn't compile.

An example usage might be:

// src/lib.rs (could be anywhere really, it just needs to be a regular #[test] function)
#[cfg(test)]
#[test]
fn ui_tests() {
  let t = trybuild::TestCases::new();
  t.compile_fail("tests/fail/*.rs");
}

// tests/fail/private_field_access.rs
fn main() {
  let my_struct = MyStruct::new();
  println!("{}", my_struct.private_field);
}

This will generate a corresponding .stderr file that you can check into version control (similar to rustc's "ui" tests). When the tests are run, the file is compiled, and the output from the compiler is compared against the actual error.

This means you can catch regressions in a more fine-grained way than a simple binary "does it compile" check. For example, if you accidentally made the private field public, the above test may still fail to compile for some other reason (perhaps it doesn't implement Display). trybuild catches those regressions in a way that compile-fail doctests cannot.

When it comes time to generate the .stderr files, you can run TRYBUILD=overwrite cargo test to overwrite the existing files.

It's worth mentioning that trybuild tests (like doctests) only work for the external API of your crate, not module. If that's an issue, you could consider using cargo workspaces and a multi-crate setup (there are other reasons this might be preferable, e.g. compile times).

Upvotes: 3

Related Questions