Few questions about integration tests organization

Begin tests implementation, as application grows and some features require expected behavior on development. But few questions because the app is large, to not rewrite everything later:

  1. By reading the docs, found examples with mod:
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }
}

But I'm using separated file (submod) without mod {} construction, must I append #[cfg(test)] always (as the file header) to skip it on build or just #[test] enough for every function defined?

// mod.rs
#[cfg(test)] // <- is required?
#[test]
    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }
  1. If I use separated file, where test functions only, can I define some common attribute for all functions in file (and skip build any function for this mod in the runtime)?
  2. And must I give tests.rs or can use test.rs - both variants work, just not sure about std naming style for tests. Prefer test.rs as short.

Thanks!

  1. Integration tests shouldn't have #[cfg(test)], but you still need #[test] on each test function.

  2. If I'm understanding your question correctly, you can apply an attribute to an entire file by placing #![attribute] (note !) at the top of the file, e.g., #![cfg(feature = "foo")].

  3. You can name your integration test files whatever you want; they just need to be inside a folder named tests/ next to your Cargo.toml.

1 Like

To clarify on #2, the integration test folder tests/ is ignored when compiling your binary. The #[cfg(test)] annotation is only needed inside the src/ folder for your binary/library source.

Note also: if you put multiple top-level files in the tests/ folder, then cargo will run them sequentially.
To run integration tests in parallel, you can define one top-level file, then use mod statements to put the separate tests as submodules of the one top level.
Cargo will only pickup the one top-level file, and then all the submodules' #[test] functions will run in parallel.

2 Likes

Who said they were writing integration tests?

As far as I know there's nothing stopping you from putting unit tests in separate files.

  1. Yes, you can define and import items same as any other module. None of it is compiled without the test feature being active, i.e. cargo run will not compile the module marked #![cfg(test)]

  2. Any separate file (EDIT to clarify: for unit tests) is already unidiomatic (but not in a problematic way) so go with your gut. Every test tooling in other languages I'm familiar with supports both test and tests as there's generally no consensus. And with reason, they are both perfectly adequate.

1 Like

You are correct, I had assumed this from the post title (Few questions about integration tests organization), but that may not be meant in the sense of using the tests/ folder. It's possible they are writing "integration style" tests inside the crate source itself, where my comments don't apply.

2 Likes

Thanks for replies!

In fact, I'm not using top-level /tests directory,
thoughts to place tests as the sub-mod for every mod, like that:

  • on this example, it's test mod for parent request mod

it's because application contain lot of files, even I can re-define hierarchy in /tests namespace.
Is this my current implementation correct?

If you have only a #[test] function then you don't specifically need any cfg(test). It's never useful to write

#[cfg(test)]
#[test]

because #[test] has almost the same effect itself — the function won’t be compiled if not testing.[1]

But usually, if you have a test module, then you also will have some uses and maybe even helper functions, so you should put #[cfg(test)] on the mod in the parent module file. This disables the entire module in non-test builds. This way you won't get dead_code warnings for any of the uses or other test helper code, and lets the compiler skip parsing the entire file so your build can be a little faster.


  1. An edge case: If you use the harness = false option in Cargo, then #[cfg(test)] functions will be compiled when building a test binary, but #[test] functions will not! This rarely matters. ↩︎

4 Likes

Thanks,
Could somebody please check my current integration tests organization:

There, I have added #[cfg(test)] only to the root file (integration.rs) like that:

// tests/integration.rs

#[cfg(test)]
mod server;
#[cfg(test)]
mod user;

Children mods does not include #[cfg(test)] because parent already have it.

All functions contain #[test] annotations

// tests/path/to/some/file.rs

#[test]
fn build() {
//..

ps. not sure I want #[cfg(test)] for any file, placed under the tests directory

You don't need this. Any file within the /tests folder will be treated as a test crate.

1 Like

Thank you, removed!

Just bit confusing that any function yet require #[test] annotation under the test folder.
Maybe that's because test files may contain some helper functions (e.g. not for the run).

Exactly. It's just a way to indicate the test runner which functions are actually tests.

1 Like