Domanda

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?

È stato utile?

Soluzione

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:

  1. From tests of the concrete Foo class we know that it correctly uses/interacts with its constructor parameters.
  2. From these tests we also know that the constructor parameter is exposed as a read-only property.
  3. From a test of the FooFactory we know that it returns an instance of the concrete Foo class.
  4. Furthermore, we know from the tests of the Create method that its parameters are correctly passed to the Foo constructor.
  5. Q.E.D.

Altri suggerimenti

Well, presumably those parameters make the returned IFoo have something true about it. Test for that being true about the returned instance.

You could make FooFactory a generic class that expects Foo as a parameter, and specify that to be a mock. Calling factory.Create(...) then creates an instance of the mock, and you could then confirm that the mock received the arguments you expected.

Factories are usually not unit-tested, at least according to my experience - there is not much value in unit testing them. Because it is the implementation of that interface-implementation mapping hence it refers the concrete implementation. As such, mocking Foo is not possible to check whether it receives those parameters passed in.

On the other hand, usually DI containers work as factories so if you are using one, you would not need a factory.

It depends on what Foo does with the 2 input objects. Check for something that Foo does with them that can easily be queried against. For instance, if methods (including getters or setters) are called on the objects, provide mocks or stubs that you can verify had the expected things called. If there is something on the IFoo that can be tested against, you can pass known values in through your mock objects to test against after creation. The objects themselves should not need to be publicly available to verify that you got them passed through.

I might also verify that the expected type (Foo) is returned, depending on if the other behaviors tested depend on the difference between IFoo implementations.

If there are any error conditions that can result, I'd try to force those as well, what happens if you pass in null for either or both objects, etc. Foo would be tested separately, of course, so you can assume during a FooFactory test that Foo does what it should.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top