I've been struggling with an increasingly annoying problem regarding our unit tests that we are implementing in my team. We are attempting to add unit tests into legacy code that wasn't well designed and while we haven't had any difficulty with the actual addition of the tests we are starting to struggle with how the tests are turning out.
As an example of the problem let's say you have a method that calls 5 other methods as part of its execution. A test for this method might be to confirm that a behavior occurs as a result of one of these 5 other methods being called. So, because a unit test should fail for one reason and one reason only, you want to eliminate potential issues caused by calling these other 4 methods and mock them out. Great! The unit test executes, the mocked methods are ignored (and their behavior can be confirmed as part of other unit tests), and the verification works.
But there's a new problem - the unit test has intimate knowledge of how you confirmed that behavior and any signature changes to any of those other 4 methods in the future, or any new methods that need to be added to the 'parent method', will result in having to change the unit test to avoid possible failures.
Naturally the problem could be mitigated somewhat by simply having more methods accomplish less behaviors but I was hoping there was perhaps a more elegant solution available.
Here's an example unit test that captures the problem.
As a quick note 'MergeTests' is a unit testing class that inherits from the class we are testing and overrides behavior as needed. This is a 'pattern' we employ in our tests to allow us to override calls to external classes / dependencies.
[TestMethod]
public void VerifyMergeStopsSpinner()
{
var mockViewModel = new Mock<MergeTests> { CallBase = true };
var mockMergeInfo = new MergeInfo(Mock.Of<IClaim>(), Mock.Of<IClaim>(), It.IsAny<bool>());
mockViewModel.Setup(m => m.ClaimView).Returns(Mock.Of<IClaimView>);
mockViewModel.Setup(
m =>
m.TryMergeClaims(It.IsAny<Func<bool>>(), It.IsAny<IClaim>(), It.IsAny<IClaim>(), It.IsAny<bool>(),
It.IsAny<bool>()));
mockViewModel.Setup(m => m.GetSourceClaimAndTargetClaimByMergeState(It.IsAny<MergeState>())).Returns(mockMergeInfo);
mockViewModel.Setup(m => m.SwitchToOverviewTab());
mockViewModel.Setup(m => m.IncrementSaveRequiredNotification());
mockViewModel.Setup(m => m.OnValidateAndSaveAll(It.IsAny<object>()));
mockViewModel.Setup(m => m.ProcessPendingActions(It.IsAny<string>()));
mockViewModel.Object.OnMerge(It.IsAny<MergeState>());
mockViewModel.Verify(mvm => mvm.StopSpinner(), Times.Once());
}
How have the rest of you dealt with this or is there no great 'simple' way of handling it?
Update - I appreciate everyone's feedback. Unfortunately, and it's no surprise really, there doesn't seem to be a great solution, pattern, or practice one can follow in unit testing if the code being tested is poor. I marked the answer that best captured this simple truth.