Classes that create their own internal domain objects and simple objects are typically not a problem for testability. The problem comes when classes are creating their own dependent services internally. Then, you've got a problem cutting the testing seams. Creating these dependent services is what Dependency Injection is best at. Using your IoC container to create everything constitutes container abuse.
However, I will use my IoC container of preference to create populated collections if the contents of those collections are inherently configuration. For instance, assume we have the following class:
public class Validator<T> {
private Collection<Validation<T>> validations;
private ValidationProblemReporter reporter;
public Validator(Collection<Validation<T>> validations,
ValidationProblemReporter reporter) {
this.validations = validations;
this.reporter = reporter;
}
public validate(Collection<T> elements) {
for(Validation<T> validation : validations) {
for(T elements : elements) {
if (!validation.isValid(element)) {
String m = element.toString() + " fails " + validation.toString();
reporter.reportProblem(m);
}
}
}
}
}
we frequently use a validation pattern that calls for a Collection<Validation<T>>. These validations vary from project to project, but the Validator<T> class is the same from project to project. The actual validations used varies depending on the validation requirements of the project and the type of the object that is being validated. Each instance of Validation<T> is pure business logic, and each is properly unit tested.
Determining what to put in the Collection<Validation<T>> is simply a configuration concern. This is where it is easy to use the IoC container. Since Guice is what I typically use, that's what I'll show.
Example: Say, we are dealing with Customer objects that we are importing into the system. These customers are being imported from someone else's system into ours. So, we are not sure if each customer object is valid and we only want to import the objects that are valid. There are multiple ways to bind up the collection of validations, among them are using TypeLiteral and using a marker class. For this, I will show the marker class, but I've also used TypeLiteral.
So, since I want to validate Customer, I create a class like:
public class CustomerValidations extends ArrayList<Validation<Customer>> {
//marker class
}
Now, I'll write a marker for the validator:
public class CustomerValidator extends Validator<Customer> {
@Inject
public CustomerValidator(CustomerValidations validations,
ValidationProblemReporter reporter) {
super(validations, reporter);
}
}
To get Guice to inject the dependencies into the CustomerValidator, we'll need to bind CustomerValidations and ValidationProblemReporter. So, I would have a Guice Module that looks something like:
public MyModule extends AbstractModule {
public void configure() {
bind(ValidationProblemReporter.class).to(ConsoleReporter.class);
bind(CustomerValidations.clas).to(CustomerValidationsProvider.class);
}
}
We'll have to write the provider for the CustomerValidations:
public class CustomerValidationsProvider implements Provider<CustomerValidations> {
public CustomerValidations get() {
validations = new CustomerValidations();
validations.add(new CustomerShouldHaveAName());
validations.add(new CustomerShouldHaveAnEmailAddress());
...
return validations;
}
}
This cuts a very nice seam to configure which validations we want to run over our customers. I can easily write a unit test that asserts what validations we have configured. In fact, if order is important, then I can check that as well.
public class CustomerValidationsTest {
@Test
public void we_should_do_the_right_things_in_the_right_order() {
CustomerValidationsProvider provider = new CustomerValidationsProvider();
CustomerValidations validations = provider.get();
Iterator<Validation<Customer>> iterator = validations.iterator();
assertEquals(CustomerShouldHaveAName.class, iterator.next().getClass());
assertEquals(CustomerShouldHaveAnEmailAddress.class, iterator.next().getClass());
assertFalse("Should not have any more", iterator.hasNext());
}
}
Now, we have locked down our configuration such that if we change the validations, we have to change this test. I wouldn't always lock the configuration like this, but I often do.
Guice allows you to unit test your configuration (since it's all Java). Figuring out what needs tested and what doesn't can be an art form. You'll get a better feel for it with experience.