Инъекция компонента Spring Boot с помощью Mockito - PullRequest
0 голосов
/ 23 января 2019

Вот мое GitHub репо для воспроизведения точной проблемы.

Не уверен, что это вопрос Spring Boot или вопрос Mockito.

У меня есть следующееSpring Boot @Component class:

@Component
class StartupListener implements ApplicationListener<ContextRefreshedEvent>, KernelConstants {
    @Autowired
    private Fizz fizz;

    @Autowired
    private Buzz buzz;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // Do stuff involving 'fizz' and 'buzz'
    }
}

Таким образом, StartupListener не имеет конструктора и является намеренно Spring @Component, которому вводятся его свойства через @Autowired.

@Configuration класс, предоставляющий эти зависимости, для хорошей меры:

@Configuration
public class MyAppConfiguration {
    @Bean
    public Fizz fizz() {
        return new Fizz("OF COURSE");
    }

    @Bean
    public Buzz buzz() {
        return new Buzz(1, true, Foo.Bar);
    }
}

Я сейчас пытаюсь написать модульный тест JUnit для StartupListener, и я использую Mockito с большимуспех.Я хотел бы создать макет Fizz и Buzz экземпляр и внедрить с ними StartupListener, но я не уверен, как:

public class StartupListenerTest {
  private StartupListener startupListener;

  @Mock
  private Fizz fizz;

  @Mock
  price Buzz buzz;

  @Test
  public void on_startup_should_do_something() {
    Mockito.when(fizz.calculateSomething()).thenReturn(43);

    // Doesn't matter what I'm testing here, the point is I'd like 'fizz' and 'buzz' to be mockable mocks
    // WITHOUT having to add setter methods to StartupListener and calling them from inside test code!
  }
}

Любойидеи относительно того, как мне это сделать?


Обновление

Пожалуйста, ознакомьтесь с моим репозиторием GitHub для воспроизведения этой конкретной проблемы.

Ответы [ 5 ]

0 голосов
/ 02 февраля 2019

Если вы измените свой StartupListenerTest, чтобы сосредоточиться только на классе StartupListener

т.е. добавить класс в аннотацию SpringBootTest

@SpringBootTest(classes= {StartupListener.class})

Вы получите другую ошибку, но она больше сфокусирована на классе, который вы пытаетесь протестировать.

onApplicationEvent метод сработает до запуска теста. Это означает, что вы не инициализировали свой макет с помощью when(troubleshootingConfig.getMachine()).thenReturn(machine);, поэтому машина не возвращается при вызове getMachine (), следовательно, NPE.

Лучший подход к решению этой проблемы зависит от того, чего вы пытаетесь достичь с помощью теста. Я бы использовал файл application-test.properties для настройки TroubleShootingConfig, а не @MockBean. Если все, что вы делаете в своем onApplicationEvent - это ведение журнала, вы можете использовать @SpyBean, как предложено в другом ответе на этот вопрос. Вот как ты мог это сделать.

Добавить application-test.properties в папку ресурсов, чтобы она находилась в пути к классам:

troubleshooting.maxChildRestarts=4
troubleshooting.machine.id=machine-id
troubleshooting.machine.key=machine-key

Добавить @Configuration к TroubleshootingConfig

@Configuration
@ConfigurationProperties(prefix = "troubleshooting")
public class TroubleshootingConfig {
    private Machine machine;
    private Integer maxChildRestarts;
    ... rest of the class

Измените StartupListenerTest, чтобы сосредоточиться на классах, которые вы тестируете, и следите за TroubleshootingConfig. Вам также необходимо @EnableConfigurationProperties

@RunWith(SpringRunner.class)
@SpringBootTest(classes= {TroubleshootingConfig.class, StartupListener.class})
@EnableConfigurationProperties
public class StartupListenerTest   {
    @Autowired
    private StartupListener startupListener;

    @SpyBean
    private TroubleshootingConfig troubleshootingConfig;

    @MockBean
    private Fizzbuzz fizzbuzz;

    @Mock
    private TroubleshootingConfig.Machine machine;

    @Mock
    private ContextRefreshedEvent event;

    @Test
    public void should_do_something() {
        when(troubleshootingConfig.getMachine()).thenReturn(machine);
        when(fizzbuzz.getFoobarId()).thenReturn(2L);
        when(machine.getKey()).thenReturn("FLIM FLAM!");

        // when
        startupListener.onApplicationEvent(event);

        // then
        verify(machine).getKey();
    }
}
0 голосов
/ 01 февраля 2019

Вот простой пример, который использует просто Spring.

package com.stackoverflow.q54318731;

import static org.junit.Assert.*;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;

@SuppressWarnings("javadoc")
public class Answer {

    /** The Constant SPRING_CLASS_RULE. */
    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    /** The spring method rule. */
    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    static final AtomicInteger FIZZ_RESULT_HOLDER = new AtomicInteger(0);
    static final int FIZZ_RESULT = 43;

    static final AtomicInteger BUZZ_RESULT_HOLDER = new AtomicInteger(0);;
    static final int BUZZ_RESULT = 42;

    @Autowired
    ConfigurableApplicationContext configurableApplicationContext;

    @Test
    public void test() throws InterruptedException {
        this.configurableApplicationContext
            .publishEvent(new ContextRefreshedEvent(this.configurableApplicationContext));

        // wait for it
        TimeUnit.MILLISECONDS.sleep(1);
        assertEquals(FIZZ_RESULT, FIZZ_RESULT_HOLDER.get());
        assertEquals(BUZZ_RESULT, BUZZ_RESULT_HOLDER.get());
    }

    @Configuration
    @ComponentScan //so we can pick up the StartupListener 
    static class Config {

        final Fizz fizz = Mockito.mock(Fizz.class);

        final Buzz buzz = Mockito.mock(Buzz.class);

        @Bean
        Fizz fizz() {

            Mockito.when(this.fizz.calculateSomething())
                .thenReturn(FIZZ_RESULT);
            return this.fizz;
        }

        @Bean
        Buzz buzz() {

            Mockito.when(this.buzz.calculateSomethingElse())
                .thenReturn(BUZZ_RESULT);
            return this.buzz;
        }
    }

    @Component
    static class StartupListener implements ApplicationListener<ContextRefreshedEvent> {

        @Autowired
        private Fizz fizz;

        @Autowired
        private Buzz buzz;

        @Override
        public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
            FIZZ_RESULT_HOLDER.set(this.fizz.calculateSomething());
            BUZZ_RESULT_HOLDER.set(this.buzz.calculateSomethingElse());
        }
    }

    static class Fizz {
        int calculateSomething() {
            return 0;
        }

    }

    static class Buzz {
        int calculateSomethingElse() {
            return 0;
        }
    }
}
0 голосов
/ 28 января 2019

Вы можете использовать @SpyBean вместо @MockBean, SpyBean оборачивает реальный бин, но позволяет проверять вызов метода и макетировать отдельные методы, не затрагивая другие методы реального бина.

  @SpyBean
  private Fizz fizz;

  @SpyBean
  price Buzz buzz;
0 голосов
/ 31 января 2019

вы можете сделать то же самое,

@RunWith(MockitoJUnitRunner.class)
public class StartupListenerTest {

  @Mock
  private Fizz fizz;

  @Mock
  price Buzz buzz;

  @InjectMocks
  private StartupListener startupListener;

  @Test
  public void on_startup_should_do_something() {
Mockito.when(fizz.calculateSomething()).thenReturn(43);
....
  }
}
0 голосов
/ 23 января 2019

Вы можете использовать @MockBean для макетирования bean-компонентов в ApplicationContext

Мы можем использовать @MockBean для добавления фиктивных объектов в контекст приложения Spring.Макет заменит любой существующий бин того же типа в контексте приложения.

Если бин того же типа не определен, будет добавлен новый.Эта аннотация полезна в интеграционных тестах, где необходимо смоделировать конкретный компонент, например внешнюю службу.

Чтобы использовать эту аннотацию, мы должны использовать SpringRunner для запуска теста:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class MockBeanAnnotationIntegrationTest {

@MockBean
private Fizz fizz;
 }

И я также предложу использовать @SpringBootTest

Аннотация @SpringBootTest говорит Spring Boot пойти и искать основной класс конфигурации (одинс @SpringBootApplication, например), и используйте его для запуска контекста приложения Spring.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...