Test field-injected class in Spring boot test without using Spring container

If you have some experience with Java, it is quite natural, but I think that there are people who have the same problems as me and feel like "Oh, that's right", so I will write it. It's just a reflection story. However, whether this method is correct or incorrect as a test design is different knowledge, so it is only described as one of the methods. (There is also a possibility of anti-patterns!) I wrote it, but it is also the method that inspired me to go with the design using constructor injection in the future.

Problems when testing using Spring functions (containers)

In the project I'm working on, it takes about 3 minutes (on the rented PC) to load various information to launch the application, and the test is slow to run. </ b> I wanted to cycle unit tests with good rhythm, but this time was taken for each test using the Spring container, which was enough for the reason I didn't want to test.

For example, when calling the Spring container in the test as follows (Spring boot ver 1.X)

Tested class


@Service
public class ParentService {

    @Autowired
    ChildService childService;//Field injection

    public SampleForm generateSample(int n) {
        //Inject here
        int calNumber = childService.calculate(n);
        return this.prepared(calNumber);
    }

    private SampleForm prepared(int calNumber) {
        int newN = calNumber * 2;
        SampleForm sample = new SampleForm();
        sample.setCalNumber(newN);
        return sample;
    }

}

test


@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfig.class)
@WebIntegrationTest(randomPort = true)
@Transactional
public class SampleTest {
    
    @Autowired
    ParentService parentService;

    @Test
If you give 2 to the public void generate method, you will get a SampleForm with 4.() {
        SampleForm sample = parentService.generateSample(2)
        assertThat(sample.getCalNumber(), is(4));
    }
}

I will do it like For example, waiting a few minutes each time you do such a short test is not enough.

So far, for example, in the above case, `ParentService.prepared` Make the method public </ b>


//Do not use Spring container function.
public class SampleTest {
    
    ParentService parentService = new ParenteService();

    @Test
If you give 2 to the public void prepared method, you will get a SampleForm with 4.() {
        SampleForm sample = parentService.prepared(2)
        assertThat(sample.getCalNumber(), is(4));
    }
}

I tried to make it possible to test with POJO by not calling the field injection method inside the method as much as possible, and passing the dependent object as an argument. Since the class called by field injection is not injected unless the Spring container is used, it will be set as null to the new POJO. If you write as follows

ParentService.childeService called in generateSample.NullPointerException occurs when caluclate.



```java

public class SampleTest {
    
    ParentService parentService = new ParentService();

    @Test
If you give 2 to the public void generate method, you will get a SampleForm with 4.() {
        /*
childService in the generateSample method.Call calculate, but in this test
        new ParentService()Since childService is null, an exception occurs.
        */
        SampleForm sample = parentService.generateSample(2)//Exception!      
        assertThat(sample.getCalNumber(), is(4));
    }
}

ChildService.The caluclate method is also public, we validate it in another test, and ParentService.If prepared can also be verified by testing



#### **`  Parentservice.Since generateSample should be correct, I think it should be correct without testing (in the simple example above). However, as a design originally, ParentService.prepared is not a method to call from the outside,<b>Is it really correct to make it public for testing? I feel different.</b>`**

Also, since it is not such a simple implementation in reality, you may want to test a method with an object injected by field injection.

I suddenly thought there. "In a dynamic language, you can dynamically access and set fields and define methods dynamically ... Well, Java also has reflections." In the language of Java, we don't usually touch fields dynamically. In JavaScript, it is natural to dynamically refer to field names and add methods to objects, but in Java development, it is usually (my shallow) to do for production code. I don't think (in my knowledge). I think there are various reasons, but I understand that a characteristic of Java is that static typing impairs security. So I didn't have the idea of using reflection, but I thought it could be used to perform the above tests.

And I wrote as follows.


public class SampleTest {
    
    
    ParentService parentService = new ParentService();

    @Before
    public void setUp() {
        Class<?> c = parentService.getClass();
        parentService = (ParentService)c.newInstance();
        Field f = c.getDeclaredField("childService");
        f.setAccessible(true);
        f.set(parentService , new ChildService());

    }

    @Test
If you give 2 to the public void generate method, you will get a SampleForm with 4.() {
        /*
childService in the generateSample method.Call calculate. With reflection
Since it is set in, the following tests are possible.
        */
        SampleForm sample = parentService.generateSample(2)        
        assertThat(sample.getCalNumber(), is(4));
    }
}

By writing as above, you can access and set the field without using the Spring container, You can test. Therefore, it is possible to test within the time until the start of the test. In the example, new is passed and `ChildService` is passed, but it is also possible to set a mock with behavior.

However, as I mentioned at the beginning, what is injection in the first place? Since I had only the knowledge of the rank, I thought about the above method, and I decided to review the design by reading back the book about Spring and reading the following blog.

reference Why Spring recommends Constructor Injection over Field Injection

that's all.

Recommended Posts