Reputation: 51
I'm using an autowired constructor in a service that when instantiated in the test class causes the @Value annotations to return null. Autowiring the dependencies directly solves the problem but the project follows the convention of using constructor based autowiring. My understanding is that instantiating the service in the test class is not creating it from the Spring IoC container which causes @Value to return null. Is there a way to create the service from the IoC container using constructor based autowiring without having to directly access the application context?
Example Service:
@Component
public class UpdateService {
@Value("${update.success.table}")
private String successTable;
@Value("${update.failed.table}")
private String failedTable;
private UserService userService
@Autowired
public UpdateService(UserService userService) {
this.userService = userService;
}
}
Example Test Service:
@RunWith(SpringJUnite4ClassRunner.class)
@SpringApplicationConfiguration(classes = {TestApplication.class})
@WebAppConfiguration
public class UpdateServiceTest {
private UpdateService updateService;
@Mock
private UserService mockUserService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
updateService = new UpdateService(mockUserService);
}
}
Upvotes: 4
Views: 975
Reputation: 20614
The @Value
is filled by a property placeholder configurer which is a post processor in the spring context. As your UpdateService
is not part of the context it is not processed.
Your setup looks a little like a unclear mixture of unit and integration test. For a unit tests you will not need a spring context at all . Simply make the @Value
annotated members package protected and set them or use ReflectionTestUtils.setField()
(both shown):
public class UpdateServiceTest {
@InjectMocks
private UpdateService updateService;
@Mock
private UserService mockUserService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(updateService, "successTable", "my_success");
updateService.failedTable = "my_failures";
}
}
For an integration test all wiring should be done by spring.
For this I added a inner config class providing the mock user service (the @Primary
is only for the case you have any other user service in your context) and the mock is stored in a static member here to have simple access to the mock from the tests afterwards.
@RunWith(SpringJUnite4ClassRunner.class)
@SpringApplicationConfiguration(classes = {TestApplication.class, UpdateServiceTest.TestAddOn.class})
@WebAppConfiguration
public class UpdateServiceTest {
@Autowired
private UpdateService updateService;
private static UserService mockUserService;
static class TestAddOn {
@Bean
@Primary
UserService updateService() {
mockUserService = Mockito.mock(UserService.class);
return mockUserService;
}
}
}
Upvotes: 2
Reputation: 5055
To make @Value
work updateService
should be inside of spring context.
The best practice for spring framework integration tests is to include application context in test context and autowiring test source in test:
...
public class UpdateServiceTest {
@Autowired
private UpdateService updateService;
...
Mock userService
Option with changing userService
to protected
and considering that test and source classes are in same package.
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
updateService.userService = mockUserService;
}
Option with reflection with Whitebox:
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Whitebox.setInternalState(updateService, 'userService', mockUserService);
}
Upvotes: 2