'Mockito mock Java @Value with spring boot
Hi I have this simple code for my Spring Boot Project:
@Component
public class UserRowMapper implements RowMapper<User> {
@Value("${bug.value}")
private String id;
@Value("${wrong.value}")
private String userName;
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return User.builder()
.id(rs.getInt(id))
.userName(rs.getString(userName)).build();
}
}
what I want is to create a simple Mockito Test that will check @Value strings like so:
@ExtendWith(MockitoExtension.class)
class UserRowMapperTest {
@Mock
Environment environment;
@Mock
ResultSet resultSet;
@InjectMocks
UserRowMapper userRowMapper;
@Test
void testMapRow() {
when(environment.getProperty("user.id")).thenReturn("id");
when(environment.getProperty("user.userName")).thenReturn("userName");
try {
final User user = userRowMapper.mapRow(resultSet, anyInt());
// check if its ok
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
But I can't find a simple way to check if the value I injected is what I expect.
any ideas?
Solution 1:[1]
Unfortunately, there is no mocking mechanism for Spring's @Value
. However, you can use a simple workaround using ReflectionUtils
that serves for this purpose according to the JavaDoc:
ReflectionTestUtils is a collection of reflection-based utility methods for use in unit and integration testing scenarios.
There are often times when it would be beneficial to be able to set a non-public field, invoke a non-public setter method, or invoke a non-public configuration or lifecycle callback method when testing code involving
ReflectionTestUtils.setField(userRowMapper, "id", "my-id-value");
ReflectionTestUtils.setField(userRowMapper, "userName", "my-userName-value");
JavaDoc for ReflectionTestUtils#setField(Object, String, Object)
.
Solution 2:[2]
Add getter
methods for id
and userName
fields instead of mocking Environment
class.
@Component
public class UserRowMapper implements RowMapper<User> {
@Value("${bug.value}")
private String id;
@Value("${wrong.value}")
private String userName;
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return User.builder()
.id(rs.getInt(getId()))
.userName(rs.getString(getUserName())).build();
}
public String getId() {
return id;
}
public String getUserName() {
return userName;
}
}
While mocking:
Mockito.when(userRowMapper.getId()).thenReturn("id");
Mockito.when(userRowMapper.getUserName()).thenReturn("userName");
Also, you can use TestPropertySource
annotation to provide altogether different properties file:
@SpringBootTest
@TestPropertySource(locations = "/application2.properties")
public class TestClassTest {
@Autowired
TestClass testClass;
@Test
public void test() {
assertEquals("id", testClass.getId());
}
}
Solution 3:[3]
I would rather suggest to you to do not use inline @Value
annotation on the consumer class. As you have seen, the class testability decreases.
You can solve your problem simply creating a @Configuration
bean and injecting it to the UserRowMapper
class. In this way, using DI you can easily mock the configuration in your tests.
See below a naïve implementation.
@Configuration
public class UserRowMapperConfiguration {
@Value("${bug.value}")
private String id;
@Value("${wrong.value}")
private String userName;
public String getId() {
return id;
}
public String getUserName() {
return userName;
}
}
@Component
public class UserRowMapper implements RowMapper<User> {
private UserRowMapperConfiguration configuration;
public UserRowMapper (UserRowMapperConfiguration configuration) {
this.configuration = configuration;
}
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return User.builder()
.id(rs.getInt(this.configuration.getId()))
.userName(rs.getString(this.configuration.getUserName())).build();
}
}
@ExtendWith(MockitoExtension.class)
class UserRowMapperTest {
@Mock
UserRowMapperConfiguration configuration;
@Mock
ResultSet resultSet;
@InjectMocks
UserRowMapper userRowMapper;
@Test
void testMapRow() {
when(configuration.getId()).thenReturn("id");
when(configuration.getUserName()).thenReturn("userName");
try {
final User user = userRowMapper.mapRow(resultSet, anyInt());
// check if its ok
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
Solution 4:[4]
As thepaoloboi suggested use a configuration class to hold all your configs. Now to test that your config is pointing to the right @Value key, you create an integration test by simply loading that object using spring without loading the whole context. That way it'll be as fast as a unit test.
Here's an example:
@ExtendWith(SpringExtension.class)
@Import(UserRowMapperConfiguration.class)
@TestPropertySource(properties = { "user.id=id" , "user.userName=userName"})
class UserRowMapperConfigurationTest {
@Autowired
UserRowMapperConfiguration userRowMapperConfiguration;
@Test
void test() {
assertEquals("id",userRowMapperConfiguration.getId());
assertEquals("userName",userRowMapperConfiguration.getUserName());
}
}
and Configuration class:
@Configuration
public class UserRowMapperConfiguration {
@Value("${bug.value}")
private String id;
@Value("${wrong.value}")
private String userName;
public String getId() {
return id;
}
public String getUserName() {
return userName;
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | |
Solution 4 | Sam Deir |