In a test I inject a mock to another class which seems to work properly. However, when I check the ArrayList if it is empty the result is false although its length/size is 0. How can this happen and how can I solve this problem?
@Slf4j
@Configuration
@RequiredArgsConstructor
@Setter(onMethod_ = @SuppressFBWarnings({"EI_EXPOSE_REP2", "EI_EXPOSE_REP"}))
@Getter(onMethod_ = @SuppressFBWarnings({"EI_EXPOSE_REP2", "EI_EXPOSE_REP"}))
@EnableConfigurationProperties(MyProperties.class)
public class MyConfig {
private final MyProperties myProperties;
private final GenericApplicationContext applicationContext;
@PostConstruct
void init() {
Objects.requireNonNull(myProperties, "myProperties needs not to be null.");
if (/*myProperties.getApps().size() == 0 || */myProperties.getApps().isEmpty()) {
log.info("bla bla bla");
} else {
...
}
}
}
Here's my test class:
@ExtendWith(MockitoExtension.class)
class MyConfigTest {
@Mock
MyProperties myPropertiesMock;
@InjectMocks
MyConfig myConfig;
ApplicationContextRunner contextRunner;
@Test
void should_check_for_empty_apps() {
contextRunner = new ApplicationContextRunner()
.withPropertyValues("foobar.apps[0].name=", "foobar.apps[0].baseUrl=", "foobar.apps[0].basePath=")
;
List apps = Mockito.mock(ArrayList.class);
when(myPropertiesMock.getApps()).thenReturn(apps);
myConfig.init();
contextRunner.run(context -> {
assertThat(apps.size()).isEqualTo(0);
});
}
}
The properties class:
@Slf4j
@Validated
@Data
@Setter(onMethod_ = @SuppressFBWarnings({"EI_EXPOSE_REP2", "EI_EXPOSE_REP"}))
@Getter(onMethod_ = @SuppressFBWarnings({"EI_EXPOSE_REP2", "EI_EXPOSE_REP"}))
@ConfigurationProperties(prefix = MyProperties.CONFIG_PREFIX)
public class MyProperties implements LoggableProperties {
public static final String CONFIG_PREFIX = "foobar";
@Valid
@NestedConfigurationProperty
private List<MyEndpoint> apps = new ArrayList<>();
@Data
public static class MyEndpoint {
// @NotNull
// @NotEmpty
private String baseUrl = "";
private String basePath = "";
// @NotNull
// @NotEmpty
private String name = "";
}
}
List apps = Mockito.mock(ArrayList.class);
When you mock a class, all of its method bodies are completely discarded. Methods with a non-void
return type return the default value for that type. For size()
that's 0 (default for int
), but for isEmpty()
that's false
(default for boolean
).
You can use spy
instead of mock
to get the actual implementation for methods you don't mock. That's also where the difference between when(x.method()).thenReturn(y)
and doReturn(y).when(x).method()
differ. The former actually calls the method but discards its result in favour of the mocked return value. The latter doesn't call the method at all (but isn't type safe).
Is there a reason you're mocking the array list? I don't see you using the array list as a mock at all.