Search code examples
javaunit-testinggenericsdependency-injectionhk2

Injecting a genericized stub for testing purposes


I've currently got the following classes/interfaces laid out. The type T represents the format of data returned from DataProvider implementations. I'm using a factory so I don't need to attach type information to MyStreamingOutput. I'm using HK2 to inject the DataProviderFactory into MyStreamingOutput.

public interface DataProvider<T> {
    public T next() { ... }
    ...
}

public final class SQLDataProvider<T> {
    public SQLDataProvider(final String query, final RowMapper<T> rowMapper) { ... }
}

public interface DataProviderFactory {
    public <T> DataProvider<T> getDataProvider(final String query, final RowMapper<T> rowMapper);
    ...
}

public final class SQLDataProviderFactory {
    public <T> DataProvider<T> getDataProvider(final String query, final RowMapper<T> rowMapper) {
        return new SQLDataProvider<T>(query, rowMapper);
    }
}

public final class MyStreamingOutput implements StreamingOutput {
    public MyStreamingOutput(final DataProviderFactory dpFactory) { ... }
    @Override public void write(final OutputStream outputStream) throws IOException { ... }
}

This all works fine. Now I'm trying to set up a unit test for MyStreamingOutput, but I'm running into a couple of roadblocks. I wrote the following additional class for testing purposes:

public final class DataProviderFactoryStub implements DataProviderFactory {
    private final DataProvider dataProvider;

    public DataProviderFactoryStub() {
        this.dataProvider = new DataProviderStub();
    }

    public DataProviderFactoryStub(final DataProvider dataProvider) {
        this.dataProvider = dataProvider;
    }

    @Override
    public <T> DataProvider<T> getDataProvider(final String query, final RowMapper<T> rowMapper) {
        return this.dataProvider;
    }
}

The binding occurs in

final class QueryTestResourceConfig extends ResourceConfig {

    public QueryTestResourceConfig() {
        ...

        this.register(new AbstractBinder() {
            @Override
            protected void configure() {
                bind(DataProviderFactoryStub.class).to(DataProviderFactory.class);
            }
        });
    }

}

I can successfully inject this class into MyStreamingOutput, but it has a compiler warning because the typing information used by getDataProvider() isn't shared by the instance passed into the factory. I can't add type information to the DataProviderFactoryStub class because then it no longer implements the DataProviderFactory interface. I don't want type information on the interface because it's wrong - outside of the Stub case, the factories shouldn't care about the type returned by DataProvider instances. I'd very much like to avoid using setters for the query and rowMapper parameters because I consider it bad design in this case.

I can't shake the feeling that I'm either missing something subtle in my application of generics or something obvious in my application of dependency injection. What is the right way to address this use case? It seems like this is the kind of problem DI is meant to address, but I can't see how to fix it.


Solution

  • Unfortunately, due to type erasure, it isn't possible to do what I want. I will have to look at refactoring the existing code.