Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

once with a test context #159

Open
oriontvv opened this issue Jul 18, 2022 · 5 comments
Open

once with a test context #159

oriontvv opened this issue Jul 18, 2022 · 5 comments

Comments

@oriontvv
Copy link
Contributor

In continue of #152

For example we have few services and some shared object:

struct Shared {
    pub state: i32,
}

struct ServiceA {
    shared: Shared,
}

struct ServiceB {
    shared: Shared,
}

And if we try to test if it will fail

mod test {
    use super::*;
    use rstest::{fixture, rstest};

    #[fixture]
    // #[once_test]
    fn shared() -> Shared {
        Shared { state: 42 }
    }

    #[fixture]
    fn service_a(shared: Shared) -> ServiceA {
        ServiceA { shared }
    }

    #[fixture]
    fn service_b(shared: Shared) -> ServiceB {
        ServiceB { shared }
    }

    #[rstest]
    fn test_shared_dep(service_a: ServiceA, service_b: ServiceB) {
        let mut shared_from_a = service_a.shared;
        shared_from_a.state = -42;
        assert_eq!(service_b.shared.state, -42);
    }
}
thread 'test::test_shared_dep' panicked at 'assertion failed: `(left == right)`
  left: `42`,
 right: `-42`', src/main.rs:41:9

It would be nice to have something like #[once_test] here.

@oriontvv
Copy link
Contributor Author

Or maybe fixture_once. What do you think?

@la10736
Copy link
Owner

la10736 commented Jul 29, 2022

It could be an options .... but I've some doubts about your example: you can never write something like this in safe rust and you must wrap your shared instance in a Mutex, RefCell or something else.

Anyway this can be useful... I'll try to find some time for implement it but I'm little busy now :(

@la10736
Copy link
Owner

la10736 commented Oct 23, 2022

Ok I try to sketch a valid example

#[derive(PartialEq, Debug, Copy, Clone)]
struct Service(u32);

#[fixture]
fn random() -> u32 {
    rand::thread_rng().gen()
}

#[fixture]
#[once_test]
fn shared_random(#[from(random)] r: u32) -> u32 {
    r
}

#[fixture]
fn service_a(shared_random: &u32) -> Service {
    Service(*random)
}

#[fixture]
fn service_b(shared_random: &u32) -> Service {
    Service(*random)
}

#[fixture]
fn service_c(random: u32) -> Service {
    Service(random)
}

use once_cell::sync::Lazy
use std::sync::Mutex;

static SERVICE: Lazy<Mutex<Option<Service>>> = Lazy::new(|| Mutex::new(None));

fn check_service(s: Service) {
    let old = SERVICE.lock().unwrap();
    match *old {
        Some(old_service) => assert_ne!(old_service, s),
        None => { *old = Some(s); }
    }
}


#[rstest]
#[case::first_contest]
#[case::other_contest]
fn shared_dep(service_a: Service, service_b: Service, service_c: Service) {
    assert_eq!(service_a, service_b);
    assert_ne!(service_a, service_c);
    check_service(service_a);
}

@jsadusk
Copy link

jsadusk commented Aug 25, 2023

I'm looking at this issue and trying to see if this is what I need for my use case. I need a fixture that is like #[once] but is recreated for each test case. In other words, all the fixtures called from a single test case get one instance of this fixture, but later tests get a different instance.

My case is that I am testing a builder pattern framework. I have a mutable Builder that I can add objects to and get a handle back. Later my Builder gets consumed into some repository of objects. In other words, for one test case I need all fixtures to receive a &mut of the same Builder. The test case itself needs to receive the final built Builder and consume it.

A contrived example:

#[oncetest]
fn builder {
  Builder::default()
}

#[fixture]
fn thing1(builder: &mut Builder) -> ThingHandle {
  builder.add(Thing(1))
}

#[fixture]
fn thing2(builder: &mut Builder) -> ThingHandle {
  builder.add(Thing(2))
}

#[rstest]
fn testthings(thing1: ThingHandle, thing2: ThingHandle, builder: Builder) {
  repo = builder.build();
  repo.get(thing1);
  repo.get(thing2);
}

If this is possible now, I'd love to hear how. If not, does your #[once_fixture] idea solve this?

@la10736
Copy link
Owner

la10736 commented Aug 28, 2023

@jsadusk Use mutable reference in Rust is a pain 😢

I think that you can do it by wrap it around a mutex, use thread_local and return a guard instead a reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants