新しい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()は、コントローラのより徹底的なテストのために高く評価されます。
前もって感謝します。
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())));
}
}
完全なアプリケーションの読み込みに関心がある場合は、@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スライスのドキュメントでは、すべてのアプリが読み込まれるわけではないことが明確になっています。これはスライスの性質によるものです。
テストスライスとは、テスト用に作成されたApplicationContextをセグメント化することです。通常、MockMvcを使用してコントローラーをテストする場合、データレイヤーを気にしたくないことは確かです。代わりに、コントローラーが使用するサービスをモックし、すべてのWeb関連のインタラクションが期待どおりに機能することを検証することをお勧めします。
MockMvcを自動配線する代わりに、このようなセットアップフェーズでmockmvcオブジェクトをインスタンス化しました。
protected void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}