Макет репозитория из класса контроллера с использованием Spring Boot и JUnit

Я пишу тестовые примеры для своего класса контроллера, который является приложением Spring Boot, и я хочу написать тестовые примеры только для класса контроллера, который вызывает сервис и сервис в репозиторий. Я использую SpringBootTest, который используется для создания экземпляров всех моих bean-компонентов. Я хочу издеваться только над вызовами базы данных и внешними вызовами API.

MyController {

    @Autowired
    MyService service;

    //Api call for getDetails
    service.getDetails();
}


MyService {

   @Autowired
   MyRepository repo;

}

MyControllertest {
    @Autowired
    MyController controller;

    @Mock
    MyRepository repoMock;

    @Before
    public void setup(){
        // intit mocks
    }

    @Test
    public void myTest(){
        when(repoMock.getDetails()).thenReturn(null);
        controller.getdetails();
    }
}

Когда я запускаю тестовый пример, он не использует фиктивный репозиторий, а не использует репозиторий @Autowired, который упоминается в классе Service.

Может ли кто-нибудь объяснить мне, как издеваться над репозиторием из класса контроллера.

Публикую так много вопросов в этом блоге, но я не получаю ответов.


person Java Backend Developer    schedule 20.12.2017    source источник
comment
Не могли бы вы показать нам аннотации уровня класса MyControllertest. Например. @RunWith(SpringJUnit4ClassRunner.class).   -  person Stefan Birkner    schedule 21.12.2017


Ответы (5)


Он не использует ваши макеты, потому что вы не вводите эти макеты в свои классы контроллера / службы. Вместо этого вы проводите его автоматически.
Правильный способ сделать это -

@RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {

    @InjectMocks
    MyController controller;
    .....

}

Еще лучшим решением было бы избавиться от Field Injection и использовать Constructor Injection.

Например, в вашем классе Controller и Service. Вместо использования @Autowired на полях вы можете сделать это на Contructor. Например

class MyService {

   private final MyRepository repo;

   @Autowired
   public MyService(final MyRepository repo) {
      this.repo = repo;
   }
}

Аналогично в классе контроллера

class MyController {

    @Autowired
    private final MyService service;

    public MyController(final MyService service) {
         this.service = service
    }
}

Это поможет вам легко настроить макеты во время выполнения. Например, в своем тестовом классе вы могли бы сделать

@RunWith(MockitoJUnitRunner.class)
public class MyControllertest {

    MyController controller;
    MyService service;

    @Mock
    MyRepository repoMock;


    @Before
    public void setup(){
        // init mocks
      service = new MyService(repoMock);
      controller = new MyController(service);
    }
  ..............
}

Здесь хорошая статья о внедрении поля

person pvpkiran    schedule 21.12.2017

Это сработало для меня:

  @MockBean
  private OrderRepository orderRepository;

  @SpyBean
  private OrderService orderService;

  @InjectMocks
  private OrderController orderController;
person Kamil W    schedule 15.06.2019

Вы не должны звонить

MockitoAnnotations.initMocks(this);

в методе setUp, потому что SpringRunner уже инициализирует макеты. Вызывая initMocks, вы назначаете новый объект repoMock. Все действия Mockito, такие как when, выполняются над новым объектом, но Spring по-прежнему использует "старый" MyRepository, который был создан SpringRunner.

person Stefan Birkner    schedule 20.12.2017
comment
Я использую MockitoAnnotations.initMocks (это); в моем методе настройки. Тем не менее я не могу получить MockRepository в своем классе обслуживания, но он использует @Autowired - person Java Backend Developer; 21.12.2017
comment
Вы не должны использовать initMocks. - person Stefan Birkner; 21.12.2017

3 способа решить эту проблему:

1. Один с Mockito.

 @Mock
 MyRepository repoMock;
 @InjectMocks
 MyService service;

Это означает, что он сопоставляет поля класса MyService по типу класса и присваивает этим полям макетированные переменные. Таким образом, он внедряет макеты в вашу службу. Это не имеет ничего общего с Spring.

2. Отрефактируйте конструктор MyService, чтобы зависимости можно было внедрить через конструктор. @Autowired также работает с конструкторами, и поэтому это предпочтительный способ.

@Autowired
public MyService(MyRespoitory repository){
  this.repository = repository;
}

3. Используйте тестовую среду Springs:

https://docs.spring.io/spring-security/site/docs/current/reference/html/test-mockmvc.html.

И определите bean-компонент, который является имитацией вашего репозитория.

@Bean
public MyRepository mockMyRepository(){
  return mock(MyRespository.class);
}

Это может быть медленным и утомительным для тестирования службы.

Второй - наиболее предпочтительный и простой способ.

person JSONStatham    schedule 21.12.2017

Это отлично работает:

@WebMvcTest(MyController.class)
public class WebMockTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private MyRepository repository;

@SpyBean
private MyService service;

@InjectMocks
private MyController controller;

@Test
public void saveTest() throws Exception {
    String content = "";
    long id=Long.MAX_VALUE;

    Entity entityToSend = new Entity(id, content);
    Gson gson = new Gson();
    when(repository.save(any(Entity.class)).thenReturn(Optional.of(entityToSend));

    mockMvc.perform(MockMvcRequestBuilders
            .post("/api/save")
            .content(gson.toJson(entityToSend))
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(Long.MAX_VALUE))
            .andExpect(MockMvcResultMatchers.jsonPath("$.content").value(content));
}
person Hristo Grozdanov    schedule 02.02.2021
comment
Пожалуйста, предоставьте более подробное описание вашей программы и описание возможной проблемы. Как это исправить. Для большинства людей это бесполезно, если у вашего сообщения отсутствует описание. - person Daniel Hornik; 02.02.2021
comment
Следуйте инструкциям @DanielHornik! - person Akif; 02.02.2021