cgreen-devs / cgreen

A modern, portable, cross-language unit testing and mocking framework for C and C++
ISC License
175 stars 47 forks source link

Add support for conditional return values in mocks. #275

Closed oniboni closed 2 years ago

oniboni commented 3 years ago

Add a new constraint type, which returns a value only if an other constraint is met.

Suggestion: extend the when constraint

expect(func, when(param, is_equal_to(42), will_return(42)), will_return(24));

Im not sure, if it is really needed to differentiate between the other return constraint types (CGREEN_RETURN_POINTER_CONSTRAINT and CGREEN_RETURN_BY_VALUE_CONSTRAINT) because this information is already present in the CgreenValue type..

Any objections, or clarifications why these different types are needed?

thoni56 commented 3 years ago

Thanks for the suggestion!

I'm assuming that you have encountered situations where you want to do some "logic" on the actual parameters, not just testing them against some value.

That is not uncommon, but I also think that checking against another parameter, as you describe, is just one of such cases.

I'm looking for, and pondering on, how to make a more general feature. A few cases that I've encountered:

They all point to being able to write "ordinary code" in the mock function to turn them into spies or semi-intelligent stubs or simulators.

I have not come up with any idea on how this would work, but when I do, I will make sure that this case is handled. (And if that takes to long, or turns out impossible, then this might get supported somehow.)

If you want some feedback on the proposal as such, I think the "syntax" looks a bit unclear, it is not obvious where the different parts go or mean. The double will_return() is a bit confusing. So if we want an intermediate solution to this, I think we need to figure out a better "format", one that is easy to read and understand, which is one of the primary goal of Cgreen.

Thank you for using, and contributing your experiences with, Cgreen!

oniboni commented 3 years ago

Thank you for the quick response!

I agree on your argument, that we need a more general solution. I had the existing feature from Mockito in mind..

Actually my use case is different as well: return a value depending on a certain callee in the call stack (kind of return-if-called-by), to pin expectations to avoid out of order calls to the mocked functions.

I solved this rather ugly with two global custom constraints. One call constraint one return constraint, both setup via always_expect and misusing the side_effect_data of the call constraint to manipulate the value of the return constraint. I had to change the stored_result in the mock_ function as well...

Another solution for this could be a in_context(constraint, ...) constraint, which determines which expectation is taken into account in mocks.c::find_expectation. This could also enable multiple always_expects for mocked functions.

They all point to being able to write "ordinary code" in the mock function to turn them into spies or semi-intelligent stubs or simulators.

Or load tests ;-) Spies would be really ugly to support I guess. To get dynamic loading working on all targets could be really tough.

  • verifying a field in a structure that is an argument (there are ugly work-arounds for this)
  • capture an actual argument from one mock call and re-use in logic or as a return value in another expect() (in one project I have a custom capture constraint)
  • comparing actual arguments to each other

Sounds like a shared context and function pointer magic to me. A constraint or accessor for late evaluation would be really nice, I agree.

oniboni commented 2 years ago

Obsolete.