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

Type as test case argument #123

Closed
SiKreuz opened this issue Jun 12, 2023 · 9 comments
Closed

Type as test case argument #123

SiKreuz opened this issue Jun 12, 2023 · 9 comments

Comments

@SiKreuz
Copy link

SiKreuz commented Jun 12, 2023

I have some test cases that shall test the same method, but the different implementations of it. So I have multiple structs this method is implemented for. I was thinking about writing a macro and solve the relevant method call using something like this:

let result = <$type>::deserialize(&buffer);

This is working fine. However, I wondered if it is possible to do something similar with the test_case crate.

@luke-biel
Copy link
Collaborator

If there's a common trait connecting these types, then you should be able to use impl Trait as an arg. You can see this done in tests: https://github.com/frondeus/test-case/blob/master/tests/acceptance_cases/cases_support_generics/src/lib.rs
Does it solve your use case?

@SiKreuz
Copy link
Author

SiKreuz commented Jun 12, 2023

Thanks for the quick reply. That definitely goes into the right direction. Maybe you can help me better if I make it more precise. Roughly what I have implemented (heavily simplified):

trait Deserialize: Sized {
    fn deserialize(buffer: &Vec<u8>) -> Result<Self, &'static str>;
}

struct Model1 {
    attribute1: u8,
}

struct Model2 {
    attribute2: u8,
    attribute3: u8
}

impl Deserialize for Model1 {
    fn deserialize(buffer: &Vec<u8>) -> Result<Self, &'static str> {
        // Do some stuff
    }
}

impl Deserialize for Model2 {
    fn deserialize(buffer: &Vec<u8>) -> Result<Self, &'static str> {
        // Do some other stuff than in Model1
    }
} 

How would I now test this? I would like to test deserialize() for both structs.

@luke-biel
Copy link
Collaborator

All right, it looks that this is slightly different than I anticipated, however you can still use generics with rust's type coercion:

#[cfg(test)]
mod tests {
    use test_case::test_case;

    trait Deserialize: Sized {
        fn deserialize(buffer: &Vec<u8>) -> Result<Self, &'static str>;
    }

    #[derive(Debug)]
    struct Model1 {
        attribute1: u8,
    }

    #[derive(Debug)]
    struct Model2 {
        attribute2: u8,
        attribute3: u8,
    }

    impl Deserialize for Model1 {
        fn deserialize(buffer: &Vec<u8>) -> Result<Model1, &'static str> {
            if buffer == b"input" {
                Ok(Model1 { attribute1: 0 })
            } else {
                Err("invalid input")
            }
        }
    }

    impl Deserialize for Model2 {
        fn deserialize(buffer: &Vec<u8>) -> Result<Model2, &'static str> {
            if buffer == b"input2" {
                Ok(Model2 { attribute2: 0, attribute3: 1 })
            } else {
                Err("invalid input2")
            }
        }
    }


    #[test_case(b"input" => matches Model1 { attribute1: 0 })]
    #[test_case(b"input2" => matches Model1 { attribute1: 0 }; "will panic 1")]
    #[test_case(b"input2" => matches Model2 { attribute2: 0, attribute3: 1 })]
    #[test_case(b"input" => matches Model2 { attribute2: 0, attribute3: 1 }; "will panic 2")]
    fn test_does_deserialize<T: Deserialize>(input: &[u8]) -> T {
        T::deserialize(&input.to_vec()).unwrap()
    }
}

@SiKreuz
Copy link
Author

SiKreuz commented Jun 13, 2023

That's already way closer. Thank you! Actually, this gave me an idea on how to replace a lot of my tests through test cases.

For the particular case I was asking for I'm trying to test for an error result of the deserialize method. Something like this for example:

fn deserialize_small_buffer<T: Deserialize>(min_buff_size: usize) {
    let buffer = vec![0u8; min_buff_size - 1];

    let result = T::deserialize(&buffer);

    assert_eq!(result.is_err(), true);
    assert_eq!(result.err().unwrap(), INVALID_BUFFER_SIZE_SMALL_STR);
}

How could I realize that? Seems hard to use matches if I want to check for the result. But maybe I'm missing something.

@luke-biel
Copy link
Collaborator

I realized after writing my comment that you can't really provide the T type in this case, so neither

#[test_case(b"input" => matches Err("text of an error"))]

nor

#[test_case(6, INVALID_BUFFER_SIZE_SMALL_STR)]
fn deserialize_small_buffer<T: Deserialize>(min_buff_size: usize, expected: &str) {
    let buffer = vec![0u8; min_buff_size - 1];

    let result = T::deserialize(&buffer);

    assert_eq!(result.is_err(), true);
    assert_eq!(result.err().unwrap(), expected);
}

would work.

I could expect that there's a way to overcome this issue with

#[test_case(b"input" => matches Result::<Model1, _>::Err("text of an error")]

but this ain't the prettiest solution.

There are however closure validators https://github.com/frondeus/test-case/wiki/Syntax#closure-validator and function validators https://github.com/frondeus/test-case/wiki/Syntax#function-validator you can look at. In the latter case you may be able to create a tester method that's also generic, eg.:

fn check_err<T: Deserialize>(expected: &'static str) -> impl Fn(Result<T, &'static str>) {
    move |actual: Result<T, &'static str>| {
        assert_eq!(expected, actual.unwrap_err())
    }
}

#[test_case(SIZE => using check_err::<Model1>("expected error string")]

@SiKreuz
Copy link
Author

SiKreuz commented Jun 13, 2023

I could expect that there's a way to overcome this issue with

#[test_case(b"input" => matches Result::<Model1, _>::Err("text of an error")]

but this ain't the prettiest solution.

I also tried out that idea. Works for now and would be a solution I can work with for now.

What I don't like about the two solutions you mentioned, is that I have to hard-code the error message. Would be nicer to use the const instead. So I can change the error message later and that does not break the tests.

@luke-biel
Copy link
Collaborator

This should be possible without use of matches but it will require that Model1 and similar types implement PartialEq.

@SiKreuz
Copy link
Author

SiKreuz commented Jun 17, 2023

Can you help me out. How would I do this here?

@SiKreuz
Copy link
Author

SiKreuz commented Jun 22, 2023

Just for the record. With someting like this I got it to work.

#[test_case(b"input" => matches Result::<Model1, _>::Err(x) if x == SOME_ERROR_STRING_CONST)]

@luke-biel Thanks for the help!

@SiKreuz SiKreuz closed this as completed Jun 22, 2023
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

2 participants