7

I want to make a class testable using dependency injection. But the class creates multiple objects at runtime, and passes different values to their constructor. Here's a simplified example:

public abstract class Validator {
    private ErrorList errors;

    public abstract void validate();

    public void addError(String text) {
        errors.add(
            new ValidationError(text));
    }

    public int getNumErrors() {
        return errors.count()
    }
}

public class AgeValidator extends Validator {
    public void validate() {
        addError("first name invalid");
        addError("last name invalid");
    }
}

(There are many other subclasses of Validator.)

What's the best way to change this, so I can inject a fake object instead of ValidationError?

I can create an AbstractValidationErrorFactory, and inject the factory instead. This would work, but it seems like I'll end up creating tons of little factories and factory interfaces, for every dependency of this sort. Is there a better way?

3
  • What are you attempting to test? That the Validator creates ValidationErrors? Faking ValidationError wont help. As the code stands, the Validator is not very useful because you can call validate, but you cannot get the errors generated. Commented Jan 31, 2011 at 20:23
  • I left it out, but there are other methods that provide access to the ErrorList object. So I can test using those.
    – JW01
    Commented Jan 31, 2011 at 20:29
  • See stackoverflow.com/questions/4859523/… for related info Commented Feb 1, 2011 at 8:30

2 Answers 2

5

The simplest change to make your class unit testable is to

  1. (extract the creation code into a new (virtual) method - you already have this)
  2. subclass the tested class, overriding the creation method to create a suitable mock object instead of the real one
  3. write your unit tests :-)

Of course, this is not very nice. But it allows you to cover the class with unit tests, after which you can start refactoring towards the ideal design, without fear of breaking code with deeper changes.

Obligatory reference: Working Effectively with Legacy Code.

About that ideal design it is hard to tell much though, since your example lacks details and context. If you tell us more about what you are aiming to test and what is the role of the tested class, we might be able to help more.

1
  • That's been my general approach, but in this case there are several subclasses of Validator, and there's a lot of setup involved in the test. If I make a test subclass, then I have to also make a test subclass for every real subclass of Validator. So I'll end up duplicating a lot of my setup work. But maybe that's necessary in this case.
    – JW01
    Commented Jan 31, 2011 at 20:51
2

Normally solution for this problem is Provider pattern:

public class Validator {
    public Provider<ValidationError> errorProvider;

    public void addError(String text) {
        errors.add(errorProvider.get(text));
    }
}

Provider is something similar to factory. In this case it will return new ValidationError each time get is called.

More information can be found in Dependency Injection book in section 3.3.2 (Reinjection with the Provider pattern).

Guice, for exampe, has Provider for this. If you want something more sophisticated, you can use AssistedInject.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.