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

#[fixture] not returning the same instance when used by another fixture and a test? #180

Open
rouge8 opened this issue Feb 21, 2023 · 7 comments

Comments

@rouge8
Copy link

rouge8 commented Feb 21, 2023

I have a sample project using mockito with rstest and it looks like a fixture used by another fixture and a test are receiving different instances of the object (in this case, the server fixture is used by the my_struct fixture and test_it to get a URL, but the port differs between the two).

struct MyStruct {
    pub url: String,
}

#[cfg(test)]
mod tests {
    use rstest::{fixture, rstest};

    use super::*;

    #[fixture]
    async fn server() -> mockito::Server {
        mockito::Server::new_with_port_async(0).await
    }

    #[fixture]
    async fn my_struct(#[future] server: mockito::Server) -> MyStruct {
        return MyStruct {
            url: server.await.url(),
        };
    }

    #[rstest]
    #[tokio::test]
    async fn test_it(#[future] server: mockito::Server, #[future] my_struct: MyStruct) {
        let mut server = server.await;
        let my_struct = my_struct.await;

        // FAILS HERE
        assert_eq!(server.url(), my_struct.url);

        let _m = server
            .mock("GET", "/foo")
            .with_status(200)
            .with_body("foo bar stroke:#000 other:#000")
            .create_async()
            .await;

        let resp = reqwest::get(format!("{}/foo", my_struct.url))
            .await
            .unwrap();
        assert_eq!(resp.status(), 200);
    }
}

Is this expected behavior? Coming from pytest, I would have expected only a single mockito::Server to be created for test_it() that's used by both the test and the fixture.

Full example at https://github.com/rouge8/mockito-rstest

@la10736
Copy link
Owner

la10736 commented Feb 21, 2023

In order to have a static fixture you need to use #[once] annotation https://docs.rs/rstest/0.16.0/rstest/attr.fixture.html#once-fixture . Also in pytest you need to define a scope for your fixture to have this behavior https://docs.pytest.org/en/7.2.x/reference/fixtures.html#higher-scoped-fixtures-are-executed-first

But ....

There are some limitations when you use #[once] fixture. rstest forbid to use once fixture for:

  • async function
  • Generic function (both with generic types or use impl trait)

You can look at #141 to see how to work around this.... Rust is not python and the things can sometime be little bit more complicated.

@rouge8
Copy link
Author

rouge8 commented Feb 21, 2023

I’m not talking about a static fixture. In pytest you get this behavior from the default function scope — a single test only creates one instance of the fixture, but with rstest I’m getting two instances.

@la10736
Copy link
Owner

la10736 commented Feb 21, 2023

Sorry.... I've missed your point. You're right that's the expected behavior (the default pytest behavior is the function scope).
Due the Rust's ownership model you cannot use the same instance in two places but just share reference...

Off course it is possible to implement something like this (I've already thought about it): a scoped once instance where you compute the object only if you don't already have one for the running asked scope, but that could not be the default behavior because in this case you cannot return the ownership of the fixture or a mutable reference to it but just a &'static of the fixture.

@rouge8
Copy link
Author

rouge8 commented Feb 21, 2023

Ah right, I haven’t worked with Rust for a few months and didn’t think how ownership would make this hard…

@martijnthe
Copy link

@la10736 thanks for creating this crate!

I was also running into this just now.

Off course it is possible to implement something like this (I've already thought about it): a scoped once instance where you compute the object only if you don't already have one for the running asked scope, but that could not be the default behavior because in this case you cannot return the ownership of the fixture or a mutable reference to it but just a &'static of the fixture.

That sounds like a solution indeed.

@nicoddemus
Copy link

nicoddemus commented Jul 12, 2023

(Sorry just browsing the repository and found this issue interesting)

Rust newbie here, but would forcing the fixture to return a Rc<T> solve the ownership issue? It could be automatically detected if possible, or a new attribute if not (say #[shared_fixture] for lack of a better name).

@DanielJoyce
Copy link

Fixtures are factory methods, and every time they are called generate a new instance. Of course the #[once] fixture is called once.

One way to fix this is have a single fixture that generates a test context which all tests use, and has the proper stuff

    struct TestContext{
        my_struct: MyStruct,
        server: mockito::Server,
    }

    #[fixture]
    async fn server() -> TestContext{
        let server = mockito::Server::new_with_port_async(0).await;
        let url = server.await.url();

        TestContext {
            server,
            my_struct: MyStruct {
                url 
            }
        }
    }

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

5 participants