How to mock injected list of implementations?

Viewed 695

Component.class:

@Slf4j
@Component
@RequiredArgsConstructor
public class Component {

  private final List<MessageHandlerInterface> messageHandlerInterfaces;

  @PostConstruct
  void initInterfaces() {
    mappedInterfaces =
        messageHandlerInterfaces.stream()
            .collect(Collectors.toMap(MessageHandlerInterface::getSubject, Function.identity()));
  }

}

ComponentTest.class:

@SpringJUnitConfig
@SpringBootTest
@ActiveProfiles("test")
class ComponentTest {

  @Autowired private Component component;

  @SpyBean private List<MessageHandlerInterface> interfaces;

  @Test
  public void onMessage() throws ExecutionException, InterruptedException, TimeoutException {
    /*
     * Given
     */
    String testMessage = UUID.randomUUID().toString();
    CompletableFuture<String> completableFuture = new CompletableFuture<>();
    MessageHandlerInterface mockMessageHandlerInterface =
        new MessageHandlerInterface() {
          @Override
          public String getSubject() {
            return SUBJECT;
          }

          @Override
          public void handleMessage(Message message) {
            completableFuture.complete(new String(message.getData()));
          }
        };
    Mockito.when(interfaces.stream()).thenReturn(Stream.of(mockMessageHandlerInterface));
    natsImportDispatcher.initInterfaces();

    /*
     * When
     */
    nc.publish(SUBJECT, testMessage.getBytes());

    /*
     * Then
     */
    Mockito.verify(interfaces, Mockito.times(1)).stream();
    String message = completableFuture.get(2, TimeUnit.SECONDS);
    Assertions.assertEquals(testMessage, message);
  }
}

I want to mock the available implementations of my MessageHandlerInterface interface.

With the code above I get an error however:

org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.List]: Specified class is an interface

How would I do this instead?

1 Answers

Since you're using Mockito anyway I'd suggest the following rewrite of your unit test:

// Using MockitoJUnitRunner will significantly speed up your unit test as it won't boot up an entire application context
@RunWith(MockitoJUnitRunner.class)
@ActiveProfiles("test")
class ComponentTest {

  // Works like @Autowired but instead injects your @Mock annotated fields
  @InjectMocks private Component component;

  @Mock private List<MessageHandlerInterface> interfaces;

  @Test
  public void onMessage() throws ExecutionException, InterruptedException, TimeoutException {
     /*
      * Given
      */
    String testMessage = UUID.randomUUID().toString();
    CompletableFuture<String> completableFuture = new CompletableFuture<>();

    // Instantiate the list and add the mocked MessageHandlerInterface to it
    interfaces = new ArrayList<>();
    interfaces.add(new MessageHandlerInterface() {
      @Override
      public String getSubject() {
        return SUBJECT;
      }

      @Override
      public void handleMessage(Message message) {
        completableFuture.complete(new String(message.getData()));
      }
    });

    natsImportDispatcher.initInterfaces();

    /*
     * When
     */
    nc.publish(SUBJECT, testMessage.getBytes());

    /*
     * Then
     */
    Mockito.verify(interfaces, Mockito.times(1)).stream();
    String message = completableFuture.get(2, TimeUnit.SECONDS);
    Assertions.assertEquals(testMessage, message);
}
Related