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

Support Matcher when using annotation #12

Open
nathanielsimard opened this issue Jun 5, 2019 · 14 comments
Open

Support Matcher when using annotation #12

nathanielsimard opened this issue Jun 5, 2019 · 14 comments
Labels
enhancement New feature or request

Comments

@nathanielsimard
Copy link
Owner

nathanielsimard commented Jun 5, 2019

When using mock-it annotation :

#[mock_it]
trait MyTrait {
    fn myFn(&self, aValue: i32) -> i32;
}

There is no way to use matcher like :

let mock = MyTraitMock::new();
mock.myFn.given(Any).will_return(42);

Potential Solutions :

  • Make Matcher the only behavior possible
  • Have two given functions with different signature
let mock = MyTraitMock::new();
mock.myFn.given(4).will_return(8);
mock.myFn.given_any(Any).will_return(42);
  • Have two annotations for trait:
#[mock_it(Matcher)]
trait MyTrait {
    fn myFn(&self, aValue: i32) -> i32;
}
  • Have a special annotation for functions inside trait:
#[mock_it]
trait MyTrait {
    #[matcher]
    fn myFn(&self, aValue: i32) -> i32;
}
@nathanielsimard nathanielsimard added the enhancement New feature or request label Jun 5, 2019
@AzureMarker
Copy link
Contributor

I just thought of one way we can solve for most use cases (mocking for any input). Add a given_any method to Mock and change Mock's rules to be Rule<Matcher<I>, O>. Then Given will insert Val(I) when it is called, instead of just I.

Is this similar to your second possible solution (Have two given functions with different signature )? I am not sure what that code example is trying to show that is different than the current behavior.

Possibly unrelated, but there may be a precedence issue where the Any rule is encountered before the more specific rule, causing all rules input after the Any rule to be ignored.

@nathanielsimard
Copy link
Owner Author

Yes this is the same thing as the second possible solution. This is probably the simplest solution to implement and the most flexible.

As of the issue about some rules overriding others, I think other mocking libraries also have it. I would expect that the rules will be tested in the same order as they were added.

@AzureMarker
Copy link
Contributor

In the following example, it seems to me that you would want the rules put in last to be used instead of the more general rule put in at the start:

// Default case
mock.function
    .given(Any)
    .will_return(true);
// A more specific rule
mock.function
    .given("test input")
    .will_return(false);

assert_eq!(mock.function("test input"), false);
assert_eq!(mock.function("test input2"), true);

Here is how Mockito, a popular Java mocking library, handles this:

For stubbing, the last-defined matching chain wins. This allows you to set up general test fixture behavior in a @Before method and override it in individual test cases if you wish, but also implies that order matters in your stubbing calls.

https://stackoverflow.com/a/34172381

@nathanielsimard
Copy link
Owner Author

I tried using two functions one given and another given_any, but it cannot work now because we use a generic for the inputs. So it is not possible to support multiple inputs that way.
For example:

#[mock_it]
trait MyTrait {
    fn myFn(&self, aValue: i32, anothervalue: i32) -> i32;
}

Would be:

mock.given(Val((4, 4)).will_return(8);

Instead of:

mock.given((Val(4), Val(4)).will_return(8);

And it would be impossible to do:

mock.given((Val(4), Any).will_return(42);

We need to find another way to support multiple inputs.

@AzureMarker
Copy link
Contributor

I wonder if we could generate an implementation to tuples containing Matcher<T> for all tuples (std only implements traits on tuples up to 12, so that limit should be fine for us too). Something like the following might be possible:

// Newtype for implementing things on tuples
struct MatcherTuple<T>(T);

// Repeat for tuples size 1-12
impl<A: PartialEq, B: PartialEq> PartialEq for MatcherTuple<(Matcher<A>, Matcher<B>)> {
    fn eq(&self, other: &Matcher<I>) -> bool {
        self.0.eq(&other.0) && self.1.eq(&other.1)
    }
}

Then we would change the mock rules to Rule<MatcherTuple<I>, O>.

@nathanielsimard
Copy link
Owner Author

I think the easiest way would be to put everything in Tuples and Matchers. We could generate an adaptor with the macro to lighten a bit the syntax when it is possible.

@AzureMarker
Copy link
Contributor

Can you give an example of how that would work? Is there a Tuple type, or are you proposing that we make one?

@nathanielsimard
Copy link
Owner Author

No I'm not proposing to make one. I think we should support Matchers and multiple inputs by changing the the macro to generate code similar to https://github.com/nathanielsimard/mock-it/blob/master/examples/multiple_inputs.rs

@AzureMarker
Copy link
Contributor

It already generates that kind of code. Did you mean to link this instead? https://github.com/nathanielsimard/mock-it/blob/master/examples/any.rs

@nathanielsimard
Copy link
Owner Author

Yes sorry about that. The generated code could also wraps the mock without the Matchers to keep the syntax lighter.

@AzureMarker
Copy link
Contributor

Do you mean to make a second mock object for each method which doesn't use matchers? Otherwise, the macro can't affect the ergonomics of the Mock methods.

@nathanielsimard
Copy link
Owner Author

No just a wrapper object or function that wraps arguments into Matchers, so an adaptor. That way, the Mock is decoupled from the api. It does not need to know which type is the input or the output, just the inner logic of how mock works.

@AzureMarker
Copy link
Contributor

AzureMarker commented Jun 17, 2019

Arguments from when the mock is called, or when we configure the mock in the test? If the latter, it is impossible because we cannot abstract over generic tuples.

@AzureMarker
Copy link
Contributor

From what I understand, you want to make a function that goes from (A, B, C) into (Matcher<A>, Matcher<B>, Matcher<C>). There's currently no way to generalize that, without pre-generating implementations for sizes 1-N.

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

No branches or pull requests

2 participants