I'm trying to write unit tests for a DAO/Repository. My intent is to mock the NamedParameterJdbcTemplate
object within and verify that the surrounding business logic doesn't do any bad (i.e.: handles nulls, etc...).
I'd also like to verify that the SQL is combined correctly. I can do this with doAnswer(...)
, but the SQL queries live in a .properties
file.
Is there a way to load a .properties
file for testing, without loading all of the other dependencies of this class?
What I've Tried
I've tried decorating the Test class with various permutations of :
@ExtendWith(SpringExtension.class)
@ExtendWith(MockitoExtension.class)
@ContextConfiguration(classes = WidgetRepositoryImpl.class)
@TestPropertySource(properties = "sql.properties")
However, turning these on and off always seems to have one of the following effects:
- Load the
NamedParameterJdbcTemplate
as a bean. This could work if I specified the nearbyAppConfig
class in theContextConfiguration
annotation, but then I'd need to load all of its underlying dependencies, and the whole point is to mock this field. - Load the Repository and mock the
NamedParameterJdbcTemplate
, but not loadsql.properties
. The SQL statements are now null.
I want to avoid ReflectionTestUtils.setField(widgetRepository, "namedParameterJdbcTemplate", "<put the sql here>")
. This works, but there are many SQL queries (the code below is very simplified). It also introduces a risk of human error (since now we're testing strings in the Test class, not the actual properties file.
The question
Is there any way to load sql.properties
for this class, but not attempt to load the NamedParameterJdbcTemplate?
Unit Test
@ExtendWith(SpringExtension.class)
@TestPropertySource(properties = "sql.properties")
class WidgetRepositoryImplTest {
@Mock
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Autowired
private WidgetRepository widgetRepository;
@Test
public void testGetWidgetById() {
// build a mock that returns a fake widget, and stores the SQL string for other tests
doAnswer(invocation -> { ...omitted for brevity... }).when(namedParameterJdbcTemplate).query(anyString(), anyMap(), any(WidgetMapper.class));
Widget widget = widgetRepository.getWidgetById("asdf-1234");
assertNotNull(widget);
}
Repository
@PropertySource("classpath:sql.properties")
@Slf4j
@Repository
public class WidgetRepositoryImpl implements WidgetRepository {
@Value("${widget-sql.selects.select-widget-by-id}")
private String selectWidgetByIdQuery;
@Autowired private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Override
public Widget getWidgetById(String id) {
Map<String, String> params = new HashMap<>();
params.put("widgetId", id);
List<Widget> results = namedParameterJdbcTemplate.query(selectWidgetByIdQuery, params, new WidgetMapper());
if (results.isEmpty()) {
return null;
}
return results.get(0);
}
sql.properties
widget-sql.selects.select-widget-by-id=select * from [WIDGETS] where WIDGET_ID=:widgetId
# a few dozen additional queries in here
CodePudding user response:
The solution is to use SpringExtension
with the @MockBean
annotation. This annotation will generate a mock implementation of the given class and provide it as a Spring bean. This means that if your class uses @Autowired
, Spring will inject the mock for you:
@ExtendWith(SpringExtension.class) // Make sure to use SpringExtension
@TestPropertySource(properties = "sql.properties")
class WidgetRepositoryImplTest {
@MockBean // Replace @Mock with @MockBean
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Autowired
private WidgetRepository widgetRepository;
// ...
}