Lets say that we are testing FooClass
with the following method:
public void Foo(string stringParameter, int intParameter, Action<Bar> successCallback, Action<Exception> errorCallback);
If the call to Foo
succeeds, the successCallback
will be called with the result of Foo
in the form of a Bar
object. If it fails, the errorCallback
will be called with an Exception
.
So the tests will look something like this:
[TestMethod]
public void Foo_UnderGivenConditions_WeExpectAGivenResult()
{
//Arrange
var fooObject = CreateFooObjectWithGivenConditions( ... );
//Act
fooObject.Foo(String.Empty, 0, (bar) => { ... }, (error) => { ... });
//Assert
Assert.AreEqual(..., ...);
}
Now, there are a lot of tests on this Foo
-method, all of them containing a call to Foo
, but not every test will care for all the parameters. Some may provide different string values, but doesn't care about the int param, and some will need to provide an error callback to assert that the right exception is thrown and so on.
Now, since all the parameters are mandatory, we have to pass a string value to the Foo
method, even though the value has no meaning for the test. There will be a lot of "I don't care"
or "some text"
. When some one else comes and read the test, they have to consider if the given value actually has a meaning for the test result or not. The same goes for the callbacks. Sometimes we need the callbacks to get to the result value or the exception, but most of the time we do not.
So lets implement an extension method, PerformFoo
, that defaults every parameter:
public static Bar PerformBar(this FooClass fooObject, string stringParameter = "some text", int intParameter = 0, Action<Bar> successCallback = null, Action<Exception> errorCallback = null);
{
Bar result = null;
var ourCallback = (bar) =>
{
result = bar;
if (successCallback != null)
sucessCallback.invoke();
}
fooObject.Foo(stringParameter, intParameter, ourCallback, errorCallback);
return result;
}
The extension method will call Foo
with default parameters, and even return the result if the successCallback is called. This lets us change the Act part of the test to something like:
//Act, all we care about is the string parameter:
fooObject.PerformFoo("A string that we care about");
//Act, we need the resulting bar when the int parameter is 10:
var bar = fooObject.PerformFoo(intParameter: 10);
//Act, we still needs to provide a callback to get the exception
fooObject.PerformFoo("SomeInvalidValueCausingAnException", errorCallback: (error) => { exceptionThrown = error; });
So the questions would be:
- Does this make the tests more readable?
- Is it easier to get what the test really tests?
- The fact that we call
PerformFoo
, which doesn't really exist on the class under test make the test less worth as an documentation? - Would dropping the extension method for a regular method taking the fooObject as the first parameter be less of a 'lie'? (e.g.
PerformFoo(fooObject, intParameter: 10)
)
How far would you go to make your tests clean and clear?
Foo
that accept different parameter subsets, and default the missing parameters? If you're already working from a fixed API forFoo
, then yes, I'd say this is a sensible approach.