Test a factory of a 3rd party class

My application uses a third party jar (no access to source, etc.) I have a factory that creates an object (call it Foo) correctly from settings, i.e.

public FooFactoryImpl implements FooFactory {
    private final Settings settings;
    private final OtherDependency other;

    @Inject
    public FooFactoryImpl(Settings settings, OtherDependency other) {
        this.settings = settings;
        this.other = other;
    }

    public Foo create(String theirArg) {
        Foo newFoo = new Foo(theirArg); // there is no no-arg constructor

        // This isn't exactly the way I do it but this is shorter and close enough
        newFoo.setParamOne(settings.get("ParamOne")); 
        newFoo.setParamTwo(settings.get("ParamTwo"));
        // etc.
    }
}

I would like to unit test this factory using Mockito - make sure the created object is configured correctly. But of course, I run into this problem; that is, because my factory calls new, I can't inject a spy.

One possible solution is to introduce something like:

public FooFactoryDumb implements FooFactory {
    public Foo create(String theirArg) {
        return new Foo(theirArg);
    }
}

And then something like:

public FooFactoryImpl implements FooFactory {
    @Inject @Dumb private FooFactory inner;

    // snip, see above

    public create(String theirArg) {
        Foo newFoo = inner.create(theirArg);
        // etc.
    }
}

This seems like a lot of boilerplate code just to enable unit testing. It smells bad to me, but I might be wrong. Is there a better way?

Answers


There is a similar but simpler way to do it: add a protected method to your factory to create a Foo:

protected Foo create(String theirArg){
    return new Foo(theirArg);
}

then in your tests of your Factory, create a Test Double of your FactoryImpl and override the create method:

private class FooFactoryImplTestDouble extends FooFactoryImpl{
    ...
    @Override
    protected Foo create(String theirArg){

        //create and return your spy here
    }
}

Create a new class:

public class FooFactory3rd {
    public Foo create3rdParty(String theirArg) {
        return new Foo(theirArg);
    }
}

Then change your class to:

public FooFactoryImpl implements FooFactory {
    private final Settings settings;
    private final OtherDependency other;
    private final FooFactory3rd fooFactory3rd;

    @Inject
    public FooFactoryImpl(Settings settings, OtherDependency other, FooFactory3rd fooFactory3rd) {
        this.settings = settings;
        this.other = other;
        this.fooFactory3rd = fooFactory3rd;
    }

    public Foo create(String theirArg) {
        Foo newFoo = fooFactory3rd.create3rdParty(theirArg);

        // This isn't exactly the way I do it but this is shorter and close enough
        newFoo.setParamOne(settings.get("ParamOne")); 
        newFoo.setParamTwo(settings.get("ParamTwo"));
        // etc.
    }
}

And in your test code:

Foo fooMock = mock(Foo.class);
FooFactory3rd fooFactory3rdMock = mock(FooFactory3rd.class);
when(fooFactory3rdMock.create3rdParty(any(String.class)).thenReturn(fooMock);

FooFactoryImpl fooFactoryImpl = new FooFactoryImpl(settings, other, fooFactory3rdMock);
fooFactoryImpl.create("any string");

This way, you can inject your fooMock. When you call fooFactoryImpl.create("any string"), your mocked Foo is called under the cover.

Or if you want to go further clean, don't even need the constructor arg of FooFactory3rd. Just declare

    private final FooFactory3rd fooFactory3rd = new FooFactory3rd();

And in your test, use reflection to change it to the mocked FooFactory3rd.


Well, it turns out that I had to use PowerMock anyway because the third party's methods were final. Since I'm already using PowerMock, I realized I can just do this:

@Before
public void setUp() throws Exception {
    Foo toReturn = PowerMockito.mock(Foo.class);
    PowerMockito.whenNew(Foo.class).withAnyArguments().thenReturn(toReturn);
}

And then I don't have to touch my original class at all.

Note: If you do this, you have to prepare both classes for PowerMock, i.e. do

@PrepareForTest( { Foo.class, FooFactoryImpl.class } )

Take a step back and think about what the contract of FooFactoryImpl is. It is that it must create a fully functional Foo, whatever that means. So if the contract of a Foo is that it does X, Y and Z, then the contract of a FooFactoryImpl is that it creates objects that do X, Y and Z.

This is a case for the kind of test in which the SUT consists of more than one class. I don't care whether you call this a unit test, an integration test, a subsystem test, a collaboration test, or some other name. The point is that the only meaningful test of FooFactoryImpl is a test that tests Foo as well. Instead of writing a test class for Foo alone, write a test class that tests the two classes jointly.

So, if the contract of Foo is to do X, Y and Z, then your test cases will do the following things with a FooFactoryImpl.

  • Call create and test that the created object does X.
  • Call create and test that the created object does Y.
  • Call create and test that the created object does Z.

I believe this is the only sensible way to attack this problem. The hard part is coming up with a convincing name for the test class.


Need Your Help

How to diff changed form file - ( .Designer InitializeComponent )

c# winforms svn merge branch

Using Suversion and WinDiff its no problem to branch / merge class-projects and web-projects.

ajax and jquery select box post method using php and mysql

php mysql ajax database

I am using ajax and jquery select box post script.

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.