0

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.

Mincong Huang
  • 5,284
  • 8
  • 39
  • 62

2 Answers2

2

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.

Community
  • 1
  • 1
Josef Reichardt
  • 2,778
  • 3
  • 22
  • 37
  • You answer is a good idea. I'll probably choose it. However, I'm curious to know how CDI injects the values into private fields? I mean, there're no setters, how they achieve it? If I apply the same mechanism, can I achieve too? (Inject them by myself) – Mincong Huang Sep 21 '16 at 14:37
  • I want to have only a no args constructor because I don't want anybody to instantiate this internal class in the API. But they can still do it actually :/ – Mincong Huang Sep 21 '16 at 14:40
  • 1
    Field injection aways requires reflection, as far as I know. I assume that it is done like this: `Field field = Example.class.getDeclaredField("fieldName"); field.setAccessible(true); field.set(instance, value);`. If you want to force that your class is only used internally you could make your constructor package private. That's no 100% solution but better than nothing. If you do this make sure your test is in the same package... ;) – Josef Reichardt Sep 22 '16 at 05:13
1

Use protected fields instead of private to be able to mock fields in unit test:

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

    @Inject
    JobContext jobContext;

    @Inject
    @BatchProperty
    String fetchSize;

    @Inject
    @BatchProperty
    String rowsPerPartition;

    // other batch properties ...

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

    @Override
    public PartitionPlan mapPartitions() throws Exception {
        // ...
    }
}
fantarama
  • 862
  • 6
  • 14
  • Actually I can mock them in private field using mockito and powermock. And I don't want to change my source code to adapt my test. And I think you misunderstood my question. I did too many mock previously, so now I want to switch to injecting approach without mocking. Any idea about how to achieve it? – Mincong Huang Sep 21 '16 at 15:16