Search code examples
javajakarta-eejunitcdi

Injecting private fields having @Inject during test?


I need to write a test PartitionMapperTest for my Java class PartitionMapper. This class has private fields with @Inject annotations but it has only a no-param constructor.

For the test, I want to create a partitionMapper before any test and inject values into its private fields. Then, tester tests the mapper's method mapPartitions and assert values. However, I don't know how to inject these values into partitionMapper.

PartitionMapper.java

@Named
public class PartitionMapper implements javax.batch.api.partition.PartitionMapper {

    @Inject
    private JobContext jobContext;

    @Inject
    @BatchProperty
    private String fetchSize;

    @Inject
    @BatchProperty
    private String rowsPerPartition;

    // other batch properties ...

    @PersistenceUnit(unitName = "h2")
    private EntityManagerFactory emf;

    @Override
    public PartitionPlan mapPartitions() throws Exception {
        // ...
    }
}

PartitionMapperTest.java

public class PartitionMapperTest {

    private PartitionMapper partitionMapper;

    @Before
    public void setUp() {
        // Prepare JobContext, batch properties to inject ...

        // Instantiation
        partitionMapper = new PartitionMapper();

        // TODO How to inject these objects into partitionMapper?
    }

    @Test
    public void testMapPartitions() throws Exception {
        PartitionPlan partitionPlan = partitionMapper.mapPartitions();
        for (Properties p : partitionPlan.getPartitionProperties()) {
            // Assertions here ...
        }
    }

    // ...
}

I actually did implement a real PartitionMapperTest based on Mockito and PowerMock, which can be seen on my GitHub. The problem is there're so many assumptions that it leads to very poor code for user-understanding. I'm looking for another solution for refactoring it.


Solution

  • Is there any reason to only have a no args constructor?

    I would recommand you to use constructor injection instead of field injection. That would solve you problem.

    For example instead of:

    public class Foo {
        @Inject
        private Bar bar;
    }
    

    do this:

    public class Foo {
        private Bar bar;
    
        public Foo(@Inject Bar bar) {
            this.bar = bar;
        }
    }
    

    If you define your injection points this way you have a clean API and your class can be used in non-cdi environments (like unit-tests).

    There are many resources about "constructor injection VS field injection"... Also on stackoverflow, e.g. https://stackoverflow.com/a/19382081/4864870.