web-dev-qa-db-ja.com

Spring Boot 1.4でのSpring MVCスライスのテストに関する問題

新しいSpring Boot 1.4 MVCテスト機能を試しています。次のコントローラーがあります。

@Controller
public class ProductController {

  private ProductService productService;

  @Autowired
  public void setProductService(ProductService productService) {
    this.productService = productService;
  }

  @RequestMapping(value = "/products", method = RequestMethod.GET)
  public String list(Model model){
    model.addAttribute("products", productService.listAllProducts());
     return "products";
  }
}

私の最小限のProductService実装は次のとおりです。

@Service
public class ProductServiceImpl implements ProductService {
  private ProductRepository productRepository;

  @Autowired
  public void setProductRepository(ProductRepository productRepository) {
    this.productRepository = productRepository;
  }

  @Override
  public Iterable<Product> listAllProducts() {
    return productRepository.findAll();
  }

}

ProductRepositoryのコードは次のとおりです。

public interface ProductRepository extends CrudRepository<Product,    
 Integer>{
}

新しい@WebMvcTestを使用して、コントローラーをテストしようとしています。私の見解は、thymeleafチームプレートです。そして、私のコントローラーのテストはこれです:

@RunWith(SpringRunner.class)
@WebMvcTest(ProductController.class)

public class ProductControllerTest {
private MockMvc mockMvc;

@Before
public void setUp() {
    ProductController productController= new ProductController();       
    mockMvc = MockMvcBuilders.standaloneSetup(productController).build();
}

@Test
public void testList() throws Exception {        
mockMvc.perform(MockMvcRequestBuilders.get("/products"))                 
.andExpect(MockMvcResultMatchers.status().isOk())                
.andExpect(MockMvcResultMatchers.view().name("products"))             
 .andExpect(MockMvcResultMatchers.model().attributeExists("products"));               
 }
}

しかし、テストを実行すると、このエラーが発生します。

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'productController': Unsatisfied dependency expressed through method 'setProductService' parameter 0: No qualifying bean of type [guru.springframework.services.ProductService] found for dependency [guru.springframework.services.ProductService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [guru.springframework.services.ProductService] found for dependency [guru.springframework.services.ProductService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

ProductControllerを適切にテストするには、問題を解決するのに助けが必要です。追加の提案andExpect()は、コントローラのより徹底的なテストのために高く評価されます。

前もって感謝します。

14
user2693135

MockMvcインスタンスを手動で構成しながら、@WebMvcTestを使用しています。 @WebMvcTestの主な目的の1つはMockMvcインスタンスを自動的に構成することであるため、これは意味がありません。さらに、手動設定ではstandaloneSetupを使用しています。つまり、依存関係を挿入するなど、テスト対象のコントローラーを完全に設定する必要があります。 NullPointerExceptionの原因となることはしていません。

@WebMvcTestを使用する場合は、setUpメソッドを完全に削除し、@Autowiredフィールドを使用して代わりに自動設定されたMockMvcインスタンスを挿入できます。

次に、ProductServiceによって使用されるProductControllerを制御するために、新しい@MockBeanアノテーションを使用して、ProductServiceに挿入される模擬ProductControllerを作成できます。

これらの変更により、テストクラスは次のようになります。

package guru.springframework.controllers;

import guru.springframework.services.ProductService;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@WebMvcTest(ProductController.class)
public class ProductControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private ProductService productService;

    @Test
    public void testList() throws Exception {
      mockMvc.perform(MockMvcRequestBuilders.get("/products"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                 .andExpect(MockMvcResultMatchers.view().name("products"))
                 .andExpect(MockMvcResultMatchers.model().attributeExists("products"))
               .andExpect(MockMvcResultMatchers.model().attribute("products",
                        Matchers.is(Matchers.empty())));

    }
}
13
Andy Wilkinson

完全なアプリケーションの読み込みに関心がある場合は、@SpringBootTestではなく@AutoConfigureMockMvcと組み合わせて@WebMvcTestを使用してみてください。

私はかなり長い間この問題に苦労してきましたが、ついに全体像をつかむことができました。
インターネット上の多くのチュートリアル、 これまでに見つけた公式のSpringドキュメントも 、@WebMvcTest;を使用してコントローラーをテストできることを述べます。それは完全に正しいですが、物語の半分はまだ省略しています。
このような注釈のjavadocで指摘されているように、@WebMvcTestはコントローラーのテストのみを目的としており、アプリのすべてのBeanをまったくロードしません、これは仕様によるものです。
_@Componentscanのような明示的なBeanスキャン注釈とさえ互換性がありません。

この問題に興味のある人は誰でも注釈のjavadocを読むことをお勧めします(これは30行の長さで、凝縮された有用な情報が詰め込まれています)。

from 注釈タイプWebMvcTest

このアノテーションを使用すると、完全な自動構成が無効になり、代わりにMVCテストに関連する構成(つまり、@Controller@ControllerAdvice@JsonComponent Filter、WebMvcConfigurerおよびHandlerMethodArgumentResolver Beans。ただし、@Component@Service、または@Repository Beansではありません。 [...]完全なアプリケーション構成をロードしてMockMVCを使用する場合は、この注釈ではなく@SpringBootTest@AutoConfigureMockMvcと組み合わせて検討する必要があります

そして実際には、@SpringBootTest + @AutoConfigureMockMvcのみが私の問題を修正し、@WebMvcTestを使用する他のすべてのアプローチは必要なBeanの一部をロードできませんでした。

編集

Springのドキュメントについて作成したコメントは、@WebMvcTestを使用するとsliceが暗示されることを知らなかったため、取り戻しました。実際、MVCスライスのドキュメントでは、すべてのアプリが読み込まれるわけではないことが明確になっています。これはスライスの性質によるものです。

Spring Boot 1.4のカスタムテストスライス

テストスライスとは、テスト用に作成されたApplicationContextをセグメント化することです。通常、MockMvcを使用してコントローラーをテストする場合、データレイヤーを気にしたくないことは確かです。代わりに、コントローラーが使用するサービスをモックし、すべてのWeb関連のインタラクションが期待どおりに機能することを検証することをお勧めします。

21
Antonio

MockMvcを自動配線する代わりに、このようなセットアップフェーズでmockmvcオブジェクトをインスタンス化しました。

protected void setUp() {
        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
0
cammando