Faking objects in Java using Mockito
One of the well-used Java Isolation (Mocking) frameworks is called Mockito which is easy to use, and has an API that reminds me of FakeItEasy, Isolator or Moq (as well as others). Creating a new fake object using Mockito is as simple as writing mock(SomeDependecy.class).Mockito also enables automatically creating fakes, spies (partial fakes) and classes with dependencies using annotations (similar to attributes in Java):
public class ArticleManagerTest extends SampleBaseTestCase { @Mock private ArticleCalculator calculator; @Mock(name = "database") private ArticleDatabase dbMock; // note the mock name attribute @Spy private UserProvider userProvider = new ConsumerUserProvider(); @InjectMocks private ArticleManager manager; @Test public void shouldDoSomething() { manager.initiateArticle(); verify(database).addListener(any(ArticleListener.class)); } } public class SampleBaseTestCase { @Before public void initMocks() { MockitoAnnotations.initMocks(this); } }In the example above annotations are used to create fake objects and a real object which uses them. The end result is very similar to using an AutoMocking container.
What about NUnit?
I wanted to see if I can create similar functionality in the .NET world. At first I’ve tried using PostSharp but unfortunately the test runners had problems finding the new injected code. Instead I’ve decided to use NUnit’s ITestAction - a simple AOP for unit tests that seemed like a good fit.I’ve created a simple attribute to mark fields that I wanted faked and named it (FakeItAttribute) and create a new attribute to enable discovery and creation of fake object for fields with that attribute:
NUnit has had the ability to execute code upon these events by decorating fixture classes and methods with the appropriate NUnit- provided attributes. Action Attributes allow the user to create custom attributes to encapsulate specific actions for use before or after any test is run.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false)] public abstract class AutoFakeAttributeBase : Attribute, ITestAction { private readonly IFakeHelper _fakeHelper; protected AutoFakeAttributeBase(IFakeHelper fakeHelper) { _fakeHelper = fakeHelper; } private IEnumerable<FieldInfo> _testFields; public void BeforeTest(TestDetails details) { var isTestFixture = details.Method == null; if (isTestFixture) { DiscoverFieldsToFake(details); return; } foreach (var testField in _testFields) { var fakeObject = _fakeHelper.DynamicallyCreateFakeObject(testField.FieldType); testField.SetValue(details.Fixture, fakeObject); } } public void AfterTest(TestDetails details) { } private void DiscoverFieldsToFake(TestDetails details) { _testFields = details.Fixture.GetType() .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Where(testField => testField.CustomAttributes.Any(data => data.AttributeType == typeof(FakeItAttribute))); } public ActionTargets Targets { get { return ActionTargets.Test | ActionTargets.Suite; } } }I’ve inherited that attribute with AutoFakeItEasyAttribute that uses FakeItEasy and reflection to create fake objects.
And now I can write tests that automatically create fake objects by adding that attribute.
[TestFixture, AutoFakeItEasy] public class UsingSimpleClassTests { [FakeIt] private IDependency _fakeDependency; // Not faked private IDependency _uninitializedDependency; [Test] public void FakesCreatedAutomatically() { Assert.That(_fakeDependency, Is.Not.Null); } [Test] public void FieldsWithoutAttributesAreNotInitialized() { Assert.That(_uninitializedDependency, Is.Null); } }Quite cool, and yes it’s on GitHub.
Now what
I’m not sure if I like the use of fields in unit tests. I’ve seen it misused to create unreadable (and unmaintainable) tests more often than not – but at least now the fake fields are will be initialized between test runs. Now I know it’s possible to create AutoFaking using attributes and I considering adding more features until I fully implement AutoFakes.Until then – happy coding…
No comments:
Post a Comment