【问题标题】:Spring Security testing with Mockito - Unable to create SecurityContext使用 Mockito 进行 Spring Security 测试 - 无法创建 SecurityContext
【发布时间】:2023-02-22 04:03:29
【问题描述】:

我已经编写了一个测试方法来测试对 API 的 POST 请求。每次我运行它时,它都会返回此错误:无法创建 SecurityContext。

我是 Java 和 Spring 以及安全方面的初学者(所以请对我温柔一点)。我正在参加一个在线课程,该课程要求我们创建一个食谱 API,然后使用 Spring Security 对其进行保护。我已经使用 Postman 验证了所有端点,但无法通过使用 Mockito 和 mockMVC 的测试。

Java 11,弹簧引导 2.7.8

配方控制器

这里只包括 POST 请求。

package cn.RecipeAPI.Controllers;

import cn.RecipeAPI.Exceptions.NoSuchRecipeException;
import cn.RecipeAPI.Models.Recipe;
import cn.RecipeAPI.Services.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/recipes")
public class RecipeController {
    @Autowired
    RecipeService recipeService;

    @PostMapping
    public ResponseEntity<?> createNewRecipe(@RequestBody Recipe recipe, Authentication authentication) {
        try {
            Recipe insertedRecipe = recipeService.createNewRecipe(recipe, authentication);
            return ResponseEntity.created(insertedRecipe.getLocationURI()).body(insertedRecipe);
        } catch (IllegalStateException e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
}

食谱服务

package cn.RecipeAPI.Services;

import cn.RecipeAPI.Exceptions.NoSuchRecipeException;
import cn.RecipeAPI.Models.CustomUserDetails;
import cn.RecipeAPI.Models.Recipe;
import cn.RecipeAPI.Models.Review;
import cn.RecipeAPI.Repositories.RecipeRepo;
import cn.RecipeAPI.Repositories.UserRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.security.core.Authentication;

import java.util.List;
import java.util.Optional;

@Service
public class RecipeService {
    @Autowired
    RecipeRepo recipeRepo;
    @Autowired
    UserRepo userRepo;

    @Transactional
    public Recipe createNewRecipe(Recipe recipe, Authentication authentication) throws IllegalStateException {
        CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
        recipe.setUser(userRepo.getReferenceById(userDetails.getId()));
        recipe.validate();
        recipe = recipeRepo.save(recipe);
        recipe.generateLocationURI();
        return recipe;
    }
}

食谱 API 测试

这个类可能包含许多不必要的注释和东西。当我阅读许多 Stack Overflow 答案和 Spring Security 文档时,我尝试了各种修复。在这一点上,我不记得什么是必要的,什么不是。

package cn.RecipeAPI;

import cn.RecipeAPI.Controllers.RecipeController;
import cn.RecipeAPI.Exceptions.NoSuchRecipeException;
import cn.RecipeAPI.Models.*;
import cn.RecipeAPI.Security.CustomUserDetailsService;
import cn.RecipeAPI.Security.SecurityConfig;
import cn.RecipeAPI.Services.RecipeService;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
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.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;



@WebMvcTest(RecipeController.class)
@ContextConfiguration(classes = SecurityConfig.class)
@ActiveProfiles(profiles = "test")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ExtendWith(SpringExtension.class)
public class RecipeApiApplicationTests {
    @Mock
    private Authentication authentication;
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private RecipeService recipeService;
    @MockBean
    private CustomUserDetailsService customUserDetailsService;

    // I'm not sure if either of these are needed, but I'm going to leave them in for now
    @InjectMocks
    private RecipeController recipeController;
    @Autowired
    private WebApplicationContext context;

    @BeforeEach
    public void setup() {
        mockMvc = MockMvcBuilders
                .webAppContextSetup(context)
                .apply(springSecurity())
                .build();
    }

    // Create some test users
    // user for recipes
    UserMeta userMeta = UserMeta.builder().email("recipe@gmail.com").name("recipeUser1").build();
    Role role = Role.builder().role(Role.Roles.ROLE_USER).build();
    Set<Role> roles = Set.of(role);
    CustomUserDetails userRecipe = CustomUserDetails.builder().userMeta(userMeta).username("userRecipe").password("1234").authorities(roles).build();
    // user for reviews
    UserMeta userMeta1 = UserMeta.builder().email("review@gmail.com").name("reviewUser").build();
    CustomUserDetails userReview = CustomUserDetails.builder().userMeta(userMeta1).username("userReview").password("1234").authorities(roles).build();

    // Create some test recipes
    Review review = Review.builder().description("was just caramel").rating(3).user(userReview).build();
    Review review2 = Review.builder().description("was just egg").rating(4).user(userReview).build();

    Recipe recipe = Recipe.builder().name("test name").difficultyRating(1).minutesToMake(5)
            .ingredients(Set.of(Ingredient.builder().name("spam").amount("1 can").build()))
            .steps(Set.of(Step.builder().stepNumber(1).description("eat spam").build()))
            .locationURI(new URI("http://localhost:8080/recipes/1"))
            .reviews(Set.of(review))
            .id(1L)
            .user(userRecipe)
            .build();
    Recipe recipe2 = Recipe.builder().name("test name2").difficultyRating(2).minutesToMake(6)
            .ingredients(Set.of(Ingredient.builder().name("egg").amount("1 egg").build()))
            .steps(Set.of(Step.builder().stepNumber(1).description("crack egg").build()))
            .locationURI(new URI("http://localhost:8080/recipes/2"))
            .reviews(Set.of(review2))
            .id(2L)
            .user(userRecipe)
            .build();
    ArrayList<Recipe> recipes = new ArrayList<>(Arrays.asList(recipe, recipe2));

    public RecipeApiApplicationTests() throws URISyntaxException {
    }
@Test
    @Order(4)
    @WithUserDetails(value="userRecipe", userDetailsServiceBeanName="customUserDetailsService")
    public void testCreateNewRecipeSuccessBehavior() throws Exception {
        when(recipeService.createNewRecipe(any(Recipe.class), any(Authentication.class))).thenReturn(recipe);

        mockMvc.perform(post("/recipes")
                        //set request Content-Type header
                        .contentType("application/json")
                        //set HTTP body equal to JSON based on recipe object
                        .content(TestUtil.convertObjectToJsonBytes(recipe))
                )
                //confirm HTTP response meta
                .andExpect(status().isCreated())
                .andExpect(content().contentType("application/json"))
                //confirm Location header with new location of object matches the correct URL structure
                .andExpect(header().string("Location", containsString("http://localhost:8080/recipes/1")))

                //confirm some recipe data
                .andExpect(jsonPath("id").value(1))
                .andExpect(jsonPath("name").value("test name"))

                //confirm ingredient data
                .andExpect(jsonPath("ingredients", hasSize(1)))
                .andExpect(jsonPath("ingredients[0].name").value("spam"))
                .andExpect(jsonPath("ingredients[0].amount").value("1 can"))

                //confirm step data
                .andExpect(jsonPath("steps", hasSize(1)))
//                .andExpect(jsonPath("steps[0]").isNotEmpty())

                //confirm review data
                .andExpect(jsonPath("reviews", hasSize(1)))
                .andExpect(jsonPath("reviews[0].username").value("idk"));
    }
}

堆栈跟踪

java.lang.IllegalStateException: Unable to create SecurityContext using @org.springframework.security.test.context.support.WithUserDetails(setupBefore=TEST_METHOD, userDetailsServiceBeanName="customUserDetailsService", value="userRecipe")
    at org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.lambda$createTestSecurityContext$0(WithSecurityContextTestExecutionListener.java:126) ~[spring-security-test-5.7.6.jar:5.7.6]
    at org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.beforeTestMethod(WithSecurityContextTestExecutionListener.java:73) ~[spring-security-test-5.7.6.jar:5.7.6]
    at org.springframework.test.context.TestContextManager.beforeTestMethod(TestContextManager.java:293) ~[spring-test-5.3.25.jar:5.3.25]
    at org.springframework.test.context.junit.jupiter.SpringExtension.beforeEach(SpringExtension.java:174) ~[spring-test-5.3.25.jar:5.3.25]
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeEachCallbacks$2(TestMethodTestDescriptor.java:163) ~[junit-jupiter-engine-5.8.2.jar:5.8.2]
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeMethodsOrCallbacksUntilExceptionOccurs$6(TestMethodTestDescriptor.java:199) ~[junit-jupiter-engine-5.8.2.jar:5.8.2]
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(TestMethodTestDescriptor.java:199) ~[junit-jupiter-engine-5.8.2.jar:5.8.2]
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeEachCallbacks(TestMethodTestDescriptor.java:162) ~[junit-jupiter-engine-5.8.2.jar:5.8.2]
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:129) ~[junit-jupiter-engine-5.8.2.jar:5.8.2]
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66) ~[junit-jupiter-engine-5.8.2.jar:5.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) ~[na:na]
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) ~[na:na]
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) ~[junit-platform-engine-1.8.2.jar:1.8.2]
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107) ~[na:na]
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) ~[na:na]
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) ~[na:na]
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) ~[na:na]
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) ~[na:na]
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) ~[na:na]
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) ~[na:na]
    at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) ~[na:na]
    at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53) ~[na:na]
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99) ~[na:na]
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79) ~[na:na]
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75) ~[na:na]
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na]
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na]
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[na:na]
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) ~[na:na]
    at com.sun.proxy.$Proxy2.stop(Unknown Source) ~[na:na]
    at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193) ~[na:na]
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129) ~[na:na]
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100) ~[na:na]
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60) ~[na:na]
    at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) ~[na:na]
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113) ~[na:na]
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65) ~[na:na]
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) ~[gradle-worker.jar:na]
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) ~[gradle-worker.jar:na]
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'customUserDetailsService' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:874) ~[spring-beans-5.3.25.jar:5.3.25]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1358) ~[spring-beans-5.3.25.jar:5.3.25]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:309) ~[spring-beans-5.3.25.jar:5.3.25]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213) ~[spring-beans-5.3.25.jar:5.3.25]
    at org.springframework.security.test.context.support.WithUserDetailsSecurityContextFactory.findUserDetailsService(WithUserDetailsSecurityContextFactory.java:76) ~[spring-security-test-5.7.6.jar:5.7.6]
    at org.springframework.security.test.context.support.WithUserDetailsSecurityContextFactory.createSecurityContext(WithUserDetailsSecurityContextFactory.java:58) ~[spring-security-test-5.7.6.jar:5.7.6]
    at org.springframework.security.test.context.support.WithUserDetailsSecurityContextFactory.createSecurityContext(WithUserDetailsSecurityContextFactory.java:43) ~[spring-security-test-5.7.6.jar:5.7.6]
    at org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.lambda$createTestSecurityContext$0(WithSecurityContextTestExecutionListener.java:123) ~[spring-security-test-5.7.6.jar:5.7.6]
    ... 72 common frames omitted

其他

在阅读 Spring documentation on testing 时,我确实看到了这个注释。

我尝试将 spring-test-4.1.3.RELEASE 添加到我的依赖项中,这让一切都出错了。这是我见过的唯一提到的地方。我删除了它。

还有什么我应该包括的吗?

【问题讨论】:

  • 你的测试一团糟。您正在混合使用 JUnit4 和 JUnit5,而我不知道您想使用它们做什么。您正在使用 @WebMvcTest,但也使用 @ContextConfiguration,这实际上没有任何意义。您自动装配 mockmvc 但随后丢弃它并在设置方法中自己完成。尝试添加 spring-test-4.1.3 确实会让事情变得更糟,因为 spring boot 2.7 使用 Spring 5.3,混合来自不同版本框架的 jar 很麻烦。
  • 如果向下滚动错误堆栈,您将看到实际的错误消息:NoSuchBeanDefinitionException: No bean named 'customUserDetailsS​​ervice' available
  • 谢谢帕维尔!我没有注意到。
  • 在 Controller 中设置一些断点后,我意识到测试从未命中 Controller,所以现在我正在调查它。

标签: java spring testing spring-security


【解决方案1】:

首先选择 JUnit4 或 JUnit5 框架,但不能同时选择两者。我建议后者,因为那是最新的。这样,您应该删除测试中的一些注释。

@WebMvcTest(RecipeController.class)
@ContextConfiguration(classes = SecurityConfig.class)
@ActiveProfiles(profiles = "test")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@WebAppConfiguration
@ExtendWith(SpringExtension.class)
public class RecipeApiApplicationTests { }

当使用 @WebMvcTest 时,这将使用最小上下文自动配置 Spring Boot 以执行您的控制器。它还会为你设置MockMvc,所以你应该自动装配它。你不需要在你的课堂上使用控制器、网络应用程序等。由于 @WebMvcTest 已经包含 @ExtendWith(SpringExtension.class),您也可以删除它。您的类定义应该类似于以下内容。

@WebMvcTest(RecipeController.class)
@ActiveProfiles(profiles = "test")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RecipeApiApplicationTests { }

因为你应该自动装配 MockMvc 而不是自己创建它,所以放弃 setup 方法以及一些你不需要的字段。

@WebMvcTest(RecipeController.class)
@ActiveProfiles(profiles = "test")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RecipeApiApplicationTests {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private RecipeService recipeService;

    @MockBean(name="customUserDetailsService")
    private CustomUserDetailsService customUserDetailsService;

    // Your data fields for testing go here

注意 CustomerUserDetailsService 模拟中添加的名称。可能不是真的需要,因为它应该采用字段的名称或它将替换的现有 bean。

现在重新运行您的测试。如果这不起作用,请确保您在 @WithUserDetails 注释中使用的名称与应用程序中实际 UserDetailsService 的名称相同。

【讨论】:

  • 嘿 M,非常感谢您的详细回复。我已经应用了您建议的更改,但仍然出现相同的错误。实际上,我的 CustomUserDetailsS​​ervice 称为 CustomUserDetailsS​​ervice,导入语句对我来说看起来是正确的。 import cn.RecipeAPI.Security.CustomUserDetailsService;
  • 我在您的配置中引用了 bean 的名称不是班级的名字。
【解决方案2】:

非常感谢 M、Pavel 和 Nikita 在这方面帮助我。原来我的 SecurityConfig 类从未被我的测试选中。这是通过将 @Import(SecurityConfig.class) 添加到测试类来解决的。

来自 Nikita:从 2.7.X 开始,常规的 @Configuration bean 不会被 testMvcTest 拾取。见17.15.3. Structure @Configuration classes for inclusion in slice tests

这是最后的测试类,现在通过了。

@WebMvcTest(RecipeController.class)
@ActiveProfiles(profiles = "test")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Import(SecurityConfig.class)
public class RecipeApiApplicationTests {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private RecipeService recipeService;

    // Create some test users
    // user for recipes
    UserMeta userMeta = UserMeta.builder().email("recipe@gmail.com").name("recipeUser1").build();
    Role role = Role.builder().role(Role.Roles.ROLE_USER).build();
    Set<Role> roles = Set.of(role);
    CustomUserDetails userRecipe = CustomUserDetails.builder().userMeta(userMeta).username("userRecipe").password("1234").authorities(roles).build();
    // user for reviews
    UserMeta userMeta1 = UserMeta.builder().email("review@gmail.com").name("reviewUser").build();
    CustomUserDetails userReview = CustomUserDetails.builder().userMeta(userMeta1).username("userReview").password("1234").authorities(roles).build();

    // Create some test recipes
    Review review = Review.builder().description("was just caramel").rating(3).user(userReview).build();
    Review review2 = Review.builder().description("was just egg").rating(4).user(userReview).build();

    Recipe recipe = Recipe.builder().name("test name").difficultyRating(1).minutesToMake(5)
            .ingredients(Set.of(Ingredient.builder().name("spam").amount("1 can").build()))
            .steps(Set.of(Step.builder().stepNumber(1).description("eat spam").build()))
            .locationURI(new URI("http://localhost:8080/recipes/1"))
            .reviews(Set.of(review))
            .id(1L)
            .user(userRecipe)
            .build();
    Recipe recipe2 = Recipe.builder().name("test name2").difficultyRating(2).minutesToMake(6)
            .ingredients(Set.of(Ingredient.builder().name("egg").amount("1 egg").build()))
            .steps(Set.of(Step.builder().stepNumber(1).description("crack egg").build()))
            .locationURI(new URI("http://localhost:8080/recipes/2"))
            .reviews(Set.of(review2))
            .id(2L)
            .user(userRecipe)
            .build();
    ArrayList<Recipe> recipes = new ArrayList<>(Arrays.asList(recipe, recipe2));

@Test
    @Order(4)
    @WithMockUser("userRecipe")
    public void testCreateNewRecipeSuccessBehavior() throws Exception {
        when(recipeService.createNewRecipe(any(Recipe.class), any(Authentication.class))).thenReturn(recipe);

        mockMvc.perform(post("/recipes")//.with(user(userRecipe))
                        //set request Content-Type header
                        .contentType("application/json")
                        //set HTTP body equal to JSON based on recipe object
                        .content(TestUtil.convertObjectToJsonBytes(recipe))
                )
                //confirm HTTP response meta
                .andExpect(status().isCreated())
                .andExpect(content().contentType("application/json"))
                //confirm Location header with new location of object matches the correct URL structure
                .andExpect(header().string("Location", containsString("http://localhost:8080/recipes/1")))

                //confirm some recipe data
                .andExpect(jsonPath("id").value(1))
                .andExpect(jsonPath("name").value("test name"))

                //confirm ingredient data
                .andExpect(jsonPath("ingredients", hasSize(1)))
                .andExpect(jsonPath("ingredients[0].name").value("spam"))
                .andExpect(jsonPath("ingredients[0].amount").value("1 can"))

                //confirm step data
                .andExpect(jsonPath("steps", hasSize(1)))
                .andExpect(jsonPath("steps[0]").isNotEmpty())

                //confirm review data
                .andExpect(jsonPath("reviews", hasSize(1)))
                .andExpect(jsonPath("reviews[0].author").value("userReview"));
    }
}

【讨论】:

    猜你喜欢
    • 2017-06-11
    • 1970-01-01
    • 1970-01-01
    • 2010-09-26
    • 2015-08-12
    • 2017-10-21
    • 1970-01-01
    • 1970-01-01
    • 2019-04-09
    相关资源
    最近更新 更多