You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决测试addViewControllers时ApplicationContext加载失败的异常

问题:JUnit5测试视图时出现"Given type must be an interface!"错误

作为新手,我正在编写一个极简的JUnit5测试用例来测试视图,但无论如何配置文件,都会反复出现相同的错误。我尝试了@SpringBootTest、@AutoConfigureMockMvc、@ExtendWith等注解的所有组合,也试过自动装配MockMvc和在setup方法中构建它,但都无效。

测试文件

package com.example;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

// @ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MyViewTest {

    @Autowired
    private MockMvc mockMvc;
    // private WebApplicationContext webApplicationContext;

    private ViewControllerRegistry registry;

    @BeforeEach
    public void setup() {
        this.registry = new ViewControllerRegistry(new StaticApplicationContext());
        // this.mockMvc = MockMvcBuilders.standaloneSetup(webApplicationContext).build();
    }

    @Test
    public void testAddViewControllers() throws Exception {
        MyView view = new MyView(); // 修正原代码笔误
        view.addViewControllers(registry);

        mockMvc.perform(get("/"))
                .andExpect(status().isOk())
                .andExpect(view().name("forward:/public/index"));
    }
}

POM依赖片段

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>3.12.4</version>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>3.12.4</version>
    <scope>test</scope>
</dependency>

视图类代码

package com.example;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyView implements WebMvcConfigurer {

    @Override
    public void addViewControllers( ViewControllerRegistry registry ) {
        registry.addViewController( "/" ).setViewName( "forward:/public/index" );
        registry.setOrder( Ordered.HIGHEST_PRECEDENCE );
    }
}

异常堆栈跟踪(最终原因)

Caused by: java.lang.IllegalArgumentException: Given type must be an interface!
        at org.springframework.util.Assert.isTrue(Assert.java:121) ~[spring-core-5.3.6.jar:5.3.6]
        at org.springframework.data.repository.core.support.AbstractRepositoryMetadata.<init>(AbstractRepositoryMetadata.java:60) ~[spring-data-commons-2.4.8.jar:2.4.8]
        at org.springframework.data.repository.core.support.AnnotationRepositoryMetadata.<init>(AnnotationRepositoryMetadata.java:45) ~[spring-data-commons-2.4.8.jar:2.4.8]
        at org.springframework.data.repository.core.support.AbstractRepositoryMetadata.getMetadata(AbstractRepositoryMetadata.java:78) ~[spring-data-commons-2.4.8.jar:2.4.8]
        at org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport.getRepositoryConfigurations(RepositoryConfigurationExtensionSupport.java:103) ~[spring-data-commons-2.4.8.jar:2.4.8]
        at org.springframework.data.repository.config.RepositoryConfigurationDelegate.registerRepositoriesIn(RepositoryConfigurationDelegate.java:149) ~[spring-data-commons-2.4.8.jar:2.4.8]
        at org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport.registerBeanDefinitions(AbstractRepositoryConfigurationSourceSupport.java:62) ~[spring-boot-autoconfigure-2.4.5.jar:2.4.5]
        at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromRegistrars$1(ConfigurationClassBeanDefinitionReader.java:396) ~[spring-context-5.3.6.jar:5.3.6]
        at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:na]
        at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(ConfigurationClassBeanDefinitionReader.java:395) ~[spring-context-5.3.6.jar:5.3.6]
        at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:157) ~[spring-context-5.3.6.jar:5.3.6]
        at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:129) ~[spring-context-5.3.6.jar:5.3.6]
        at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:343) ~[spring-context-5.3.6.jar:5.3.6]
        at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:247) ~[spring-context-5.3.6.jar:5.3.6]
        at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:311) ~[spring-context-5.3.6.jar:5.3.6]
        at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:112) ~[spring-context-5.3.6.jar:5.3.6]
        at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746) ~[spring-context-5.3.6.jar:5.3.6]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564) ~[spring-context-5.3.6.jar:5.3.6]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:782) ~[spring-boot-2.4.5.jar:2.4.5]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:774) ~[spring-boot-2.4.5.jar:2.4.5]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-2.4.5.jar:2.4.5]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:339) ~[spring-boot-2.4.5.jar:2.4.5]
        at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123) ~[spring-boot-test-2.4.5.jar:2.4.5]
        at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) ~[spring-test-5.3.6.jar:5.3.6]
        at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ~[spring-test-5.3.6.jar:5.3.6]
        ... 67 common frames omitted

解决方案

1. 错误原因分析

堆栈信息显示错误来自Spring Data组件,核心原因是Spring Data尝试将一个非接口类识别为Repository。你使用@SpringBootTest会加载完整的Spring应用上下文,触发Spring Data的自动配置逻辑,它会扫描项目中的Repository类,但如果扫描到不符合要求的非接口类(比如扫描路径覆盖了非Repository类),就会抛出该异常。

而你实际只需要测试Web视图配置,完全不需要加载整个应用上下文。

2. 修改测试类配置

使用@WebMvcTest替代@SpringBootTest,这个注解只会加载Web层相关的Bean(包括WebMvcConfigurer实现类如MyView),不会加载Spring Data、数据库等无关组件,避免触发不必要的自动配置逻辑。

同时,你手动创建MyView和ViewControllerRegistry的代码是多余的,Spring会自动加载并应用MyView中的视图配置。

修改后的测试代码如下:

package com.example;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(MyView.class) // 指定加载目标WebMvc配置类
public class MyViewTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testRootPathRedirect() throws Exception {
        mockMvc.perform(get("/"))
                .andExpect(status().isOk())
                .andExpect(view().name("forward:/public/index"));
    }
}

3. 额外注意事项

  • 如果项目中确实有Spring Data依赖,确保所有Repository都是接口,并且正确标记@Repository或继承Spring Data的标准Repository接口(如JpaRepository)。
  • @WebMvcTest默认会自动配置MockMvc,无需额外添加@AutoConfigureMockMvc注解。

内容的提问来源于stack exchange,提问作者wissotajava

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 20:14:57