nrxus / faux

Struct mocking library for Rust
https://nrxus.github.io/faux/
MIT License
411 stars 14 forks source link

Proposal for API change #31

Closed nrxus closed 3 years ago

nrxus commented 3 years ago

API Changes

Mocking with arguments

when! can now accept "arguments" into the mocked methods, such as:

when!(number_adder.add(3, 4)).then_return(999);

Arguments can be partially specified:

when!(number_adder.add(3, _)).then_return(4);

or unspecified

when!(number_adder.add).then_return(777);

when! returns an object that exposes two methods:

when!(number_adder.add(3,4)).then(|| 888);
when!(number_adder.add(_,4)).then(|| 777);
when!(number_adder.add).then(|a, b| a + b);

unchecked_when

Create a new unchecked_when! macro that is the unsafe version of when!, exposing the same functionality. Use unchecked_when! to mock methods with references in the arguments or output, as this cannot be done without unsafe code.

let alice = User { /* .. */ }
unsafe { unchecked_when!(my_service.promote(&alice)).then_return(Ok(()) }
unsafe {
    unchecked_when!(my_service.promote).then(|employee| {
        if employee == alice {
            panic!("not expected")
        }

        Ok(())
    })
}

Thanks @muscovite for the 🧠

nrxus commented 3 years ago

Problem

What is the most intuitive behavior on how this would handle mocks with different arguments.

when!(my_mock.some_method(3)).then_return(666)
when!(my_mock.some_method(3)).then_return(3)
when!(my_mock.some_method).then_return(666)
when!(my_mock.some_method(5)).then_return(5)

Options:

Latest Always Wins

my_mock.some_method(3) => panic!. Latest mock does not match the argument my_mock.some_method(10) => panic!. Latest mock does not match the argument my_mock.some_method(5) => 5. Latest mock matches the argument

This is the current behavior. Right now it is okay because mock methods don't take arguments.

Latest to handle the argument

my_mock.some_method(3) => 999. Latest mock that handles the input is the closure. my_mock.some_method(10) => 999. Latest mock that handles the input is the closure. my_mock.some_method(5) => 5. Latest mock that handles the input is the specific mock.

Most "specific" that match the argument, then latest.

my_mock.some_method(3) => 3. Most specific mocks return 3 and 666, with 3 being the latest mock. my_mock.some_method(10) => 999. Only mock that handles the argument. my_mock.some_method(5) => 5. Most specific mock.

Trade-offs

These three have different trade offs in most seemingly "correct" vs complexity of both implementation and explanations (i.e., what does "specific" even mean?)

nrxus commented 3 years ago

Another alternative:

Instead of when! vs unchecked_when! just add an unchecked version to each method?

nrxus commented 3 years ago

This has been fully implemented! Just waiting on tweaking docs to do a release.

nrxus commented 3 years ago

This has been released in v0.0.9!