Automaticaly rollback static overrides when testing legacy code
When working with legacy code, writing tests requires to exploit seams to -hack- inject custom behaviour. In Working Effectively with Legacy Code, Michael Feathers explains that singleton (the anti pattern) are often good starting seams. The idea is as follow :
- create a public method to override the singleton value
- make this method deprecated, obsolete or use a TEST_ONLY_ name prefix so that it is not called elsewhere in the code
- Call this method from your test code to inject a mock or whatever so that you can test in isolation
This works fine … until you end up with an unexpected failing test [long debugging session] that is in fact the result of a test not restoring a singleton it had overriden. You can try hard not to forget, or you can use some kind of auto restore test class. Here is how it could look like in C#
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Test.Utils
{
public class CleanOverridesTest
{
private class StaticOverrider<T> : IDisposable
{
private readonly T _initialValue;
private readonly Action<T> _setter;
public StaticOverrider(T initialValue, Action<T> setter, T newValue)
{
_initialValue = initialValue;
_setter = setter;
_setter(newValue);
}
public void Dispose()
{
_setter(_initialValue);
}
}
private List<IDisposable> _toDisposeAfterEachTest;
private List<IDisposable> ToDisposeAfterEachTest
{
get
{
// NUnit enforces a single SetUp method, so we have to make sure the base SetUp method was called by subclasses
Assert.IsTrue(BaseSetUpCalled, "Override SetUp and TearDown, and call base implementation.");
return _toDisposeAfterEachTest;
}
}
[SetUp]
public virtual void SetUp()
{
_toDisposeAfterEachTest = new List<IDisposable>();
}
private bool BaseSetUpCalled
{
get { return _toDisposeAfterEachTest != null; }
}
[TearDown]
public virtual void TearDown()
{
// Overides and restores should be done in reverse order
ToDisposeAfterEachTest.Reverse();
foreach(var disposable in ToDisposeAfterEachTest)
{
try
{
disposable.Dispose();
}
catch (Exception)
{ }
}
ToDisposeAfterEachTest.Clear();
}
protected void OverrideStatic<T>(T initialValue, Action<T> setter, T newValue)
{
ToDisposeAfterEachTest.Add(new StaticOverrider<T>(initialValue, setter, newValue));
}
}
}
Later in an actual test :
[TestFixture]
public class LegacyTest : CleanOverridesTest
{
[Test]
public void LegacyShouldWorkWhenZingAndZang()
{
OverrideStatic(BigManager.Instance, BigManager.TestOnlySetInstance, new BigInstanceMock());
...
}
}
The singleton is automaticaly restored in the TearDown method of the base class. In C#, we are luky enough to have delegates so that we can pass the injection setter directly. In language without this feature, you could use reflection or anonymous class.
Of course, when the code is more reliably tested, you should try to move away from all this hack …
Leave a comment