1

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?

7
  • 2
    I am not aware of any way to check if a test fails compilation, but you could write a documentation test that uses the compile_fail attribute to assert that the method is not accessible outside the crate. Commented Feb 21, 2022 at 23:46
  • I would like the test to compile, and when run be able to check the visibility of the method outside the crate. Maybe that information isn't available at runtime. Commented Feb 21, 2022 at 23:50
  • 3
    Visibility is a compile-time property, there is no notion of it at runtime. Commented Feb 21, 2022 at 23:58
  • This might be better suited to a static code analyzer run as part of your test suite and configured to detect such a case. That said, such a test may be of little value. Martin Fowler's Public Verses Published Interfaces might help understand why. Commented Feb 22, 2022 at 0:55
  • Is the concern merely that a client might notice an undocumented public method and start to rely on it? In which case I'd say their fault for using an undocumented method; just because it's public doesn't mean its part of the stable API, and without documentation you made no promises. Or is there an additional concern? Commented Feb 22, 2022 at 0:57

1 Answer 1

3

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).

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.