Bomin
Bomin

Reputation: 1667

Unit test of Spring boot service with private fields and @PostConstruct

I have a Spring boot service defined like this

@Service
public class MyService {
    private String field1;
    private String field2;

    @Autowired
    private AnotherService anotherService

    @PostConstruct
    public void init() {
        anotherService.initField1(field1);
        anotherService.initField2(field2);
    }

    public String foo() {
        return field1 + field2;
    }
}

How should I write a unit test for foo. Well, it's more about how to deal with class fields and the PostConstruct methods.

Thanks!!

EDIT: Added AnotherService as a field as well.

Upvotes: 1

Views: 939

Answers (2)

Boris
Boris

Reputation: 24453

The following example shows a @Service Bean that uses constructor injection to obtain a required AnotherService bean:

@Service
public class MyService {
    private String field1;
    private String field2;

    private final AnotherService anotherService;

    public MyService(AnotherService anotherService) {
        this.anotherService = anotherService;
        this.anotherService.initField1(field1);
        this.anotherService.initField2(field2);
    }

    public String foo() {
        return field1 + field2;
    }
}

Note you can omit the @Autowired becuase MyService has one constructor. See here for more info.

testing with Spring
Use the @RunWith(SpringRunner.class) and @SpringBootTest to inject MyService and start using it:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyServiceTest {
    @Autowired    
    private MyService service;

    @Test
    public void testFoo() {
        String expResult = "";
        String result = service.foo();
        assertEquals(expResult, result);
    }
}

testing without Spring

public class MyServiceTest2 {
    private MyService service;

    @Before
    public void setUp() {
        service = new MyService(new AnotherService.Fake());
    }

    @Test
    public void testFoo() {
        String expResult = "";
        String result = service.foo();
        assertEquals(expResult, result);
    }
}

Here Fake is a fake implementation of the AnotherService interface which allows you to have a pure unit test.

Upvotes: 3

Doe Johnson
Doe Johnson

Reputation: 1414

Writing good, testable code can be hard. There are some pitfalls waiting for everyone to fall into sooner or later.

As a rule of thumb, try to avoid field level injection, use constructor parameter injection instead:

@Service
public class MyService {

    private AnotherService anotherService;

    @Autowired
    MyService (AnotherService anotherService) {
         this.anotherService = anotherService;
    }

}

This is the cleanest solution. You can call the constructor from your tests, spring will inject dependencies the same way at runtime. So there is no difference to deal with.

The same goes for any life cycle constructs like @PostConstruct. If you can avoid them, do it. Let the constructor handle it. If you absolutely have to keep them around, well, the only logical solution is to manually call them from your test code.

Now, how to setup services that at runtime would be autowired by the container?

For unit testing, you basically have three options (in no particular order):

  1. If the required service is rather simple and can easily be constructed, create and pass it as the framework would do.

  2. If the service has a limited interface that does not change too often, create a fake service.

  3. Use a mocking lib like mockito (spring-boot-test provides it by default).

Upvotes: 1

Related Questions