Home > OS >  JUnit 5/Spring Boot - Use Properties but Mock Dependencies
JUnit 5/Spring Boot - Use Properties but Mock Dependencies

Time:09-02

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 nearby AppConfig class in the ContextConfiguration 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 load sql.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;

    // ...
}
  • Related