Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing an abstract factory that takes parameters

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?

like image 352
JontyMC Avatar asked Feb 02 '12 23:02

JontyMC


People also ask

Can you unit test an abstract class?

Summary. There are two ways to unit test a class hierarchy and an abstract class: Using a test class per each production class. Using a test class per concrete production class.

What are the two types of unit testing techniques?

There are 2 types of Unit Testing: Manual, and Automated.

Which method is used for unit testing?

Unit Testing Techniques: Black Box Testing - Using which the user interface, input and output are tested. White Box Testing - used to test each one of those functions behaviour is tested. Gray Box Testing - Used to execute tests, risks and assessment methods.

Can we write JUnit for abstract class?

With JUnit, you can write a test class for any source class in your Java project. Even abstract classes, which, as you know, can't be instantiated, but may have constructors for the benefit of “concrete” subclasses.


2 Answers

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.
like image 156
Mark Seemann Avatar answered Oct 09 '22 23:10

Mark Seemann


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

like image 23
jason Avatar answered Oct 10 '22 01:10

jason