Assume you have a class
C defines a public method (member function)
c is an instance of
C) can either
- Return after mutating the object
cit was called on.
- Throw an exception of type
Because of (1),
C::m1 cannot be marked
const. However, if
C::m1 throws it should (by the specification and by the caller's sanity) behave as if it were. I will assume no language has native support for a
A way of testing this is to make a copy of
c (call it
c in a way in which it throws, and to test that
c == oldC after the exception is caught. However, this requires an equality operator that would otherwise not exist (and, as a consequence, adds to the amount of code that needs to be written, documented, tested, and maintained).
Assume that adding a predicate
C::willM1Fail that is
const is impossible because
m1 might fail because of external factors (other servers) and it is impossible to guarantee that the next call to
m1 will succeed.
Also, assume that making
m1 return a new object of type
C and marking it
const instead of mutating the object it was called on (which is an obvious solution) is undesired as
m1 will virtually always take path (1) and copying
C objects in production is bad, because of the performance penalty and the memory ownership complexity it will add.
The question then is: should the equality operator be defined, the exception forced (through mocking) and equality tested, or is there a better way to test that when an object of a class
C throws an exception of type
E it does not change the object (at least in the tested code paths)?
If you have a suggestion about a better design (that makes testing this property simpler), it would also be welcome.
I thought this question was sufficiently language-agnostic, however, as it was pointed out in the comments by multiple individuals, the wording of this question makes it fairly clear that it was about a C++ case. I will be marking it as C++ even though it seems to be realizable to a good extent in Rust as well (
&mut self and not mutating the object when an error is returned).
There were also suggestions about changing some of the wording (const, copy, equals) into something more language-agnostic (immutable, clone, compare). Because the answers used the wording in the original question, I think it is better to leave it as it is.
In the case that provoked this question, the solution adopted was adding another method
tryResolvingForM1, marked as
const, that throws or returns a new type
D that can be passed in a call to
The main downside is that the caller now has to call both
m1. Testing was simplified as
m1 can only be called if you have a
D object and it will not fail. Trying to get a
D object may fail but is guaranteed to not mutate the object it was called on.