Given a abstract factory implementation:
public class FooFactory : IFooFactory {
public IFoo Create(object param1, object param2) {
return new Foo(param1, param2);
}
}
What unit tests would be written for this class? How can I verify that param1 and param2 were forwarded to the creation of Foo? Do I have to make these public properties of Foo? Wouldn't that be breaking encapsulation? Or should I leave this to integration testing?
Here's how I would write one of a couple of unit tests for such a factory (with xUnit.net):
[Fact]
public void CreateReturnsInstanceWithCorrectParam1()
{
var sut = new FooFactory();
var expected = new object();
var actual = sut.Create(expected, new object());
var concrete = Assert.IsAssignableFrom<Foo>(actual);
Assert.Equal(expected, concrete.Object1);
}
Does it break encapsulation? Yes and no... a little bit. Encapsulation is not only about data hiding - more importantly, it's about protecting the invariants of objects.
Let's assume that Foo exposes this public API:
public class Foo : IFoo
{
public Foo(object param1, object param2);
public void MethodDefinedByInterface();
public object Object1 { get; }
}
While the Object1
property slightly violate the Law of Demeter, it doesn't mess with the invariants of the class because it's read-only.
Furthermore, the Object1
property is part of the concrete Foo class - not the IFoo interface:
public interface IFoo
{
void MethodDefinedByInterface();
}
Once you realize that in a loosely coupled API, concrete members are implementation details, such concrete-only read-only properties have a very low impact on encapsulation. Think about it this way:
The public Foo constructor is also a part of the API of the concrete Foo class, so just by inspecting the public API, we learn that param1
and param2
are part of the class. In a sense, this already 'breaks encapsulation', so making each parameter available as read-only properties on the concrete class doesn't change much.
Such properties provide the benefit that we can now unit test the structural shape of the Foo class returned by the factory.
This is much easier than having to repeat a set of behavioral unit tests that, we must assume, already cover the concrete Foo class. It's almost like a logical proof: