我有一个可能很愚蠢的问题/问题。首先,我对Spring中的单元测试相当陌生
我有一个API服务,使一个“预订”/“预订”的第三方API,我试图集成。我的API受Spring安全性保护,只要确保传递了有效的JWT,就可以到达我的端点。
我试图创建一个测试,当用户传递一个无效的令牌时,响应正确地是401。现在我从以下几点开始:
mockMvc.perform(post("/booking")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "eyJraWQiOiJDQnk5TFlvM2JUK0M2eVpvcWp3ZzEwTndXXC9GQWxjUURteHVHYWNZdDBhRT0iLCJhbGciOiJSUzI1NiJ9...")
.content(objectToJson(request)))
.andExpect(status().isUnauthorized());
字符串
为此,我确实得到了401,但我意识到我忘记了将承载添加到授权值。一旦我添加了承载,它现在抛出404。
mockMvc.perform
(post("/booking")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer eyJraWQiOiJDQnk5TFlvM2JUK0M2eVpvcWp3ZzEwTndXXC9GQWxjUURteHVHYWNZdDBhRT0iLCJhbGciOiJSUzI1NiJ9.....")
.content(objectToJson(request))
)
.andExpect(status().isUnauthorized());
}
型
有人能指出我做错了什么吗?
下面是完整的代码:
控制器
@Operation(summary = "Booking Creation")
@PostMapping("")
public Mono<BookingResponse> userBooking(@RequestBody @Valid BookingCreateRequestDTO bookingCreateRequestDTO,
@Parameter(hidden = true) Authentication authentication) throws JsonProcessingException {
return bookingService.processBookingRequest(bookingCreateRequestDTO);
}
型
下面是我的服务层:
public Mono<BookingResponse> processBookingRequest(BookingCreateRequestDTO bookingCreateRequestDTO) throws JsonProcessingException {
logger.info("Request Processing of Booking Request from client {}", bookingCreateRequestDTO.clientId);
WebClient webClient = WebClient.builder()
.baseUrl(xxxx)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.filter(ExchangeFilterFunctions.basicAuthentication(userName, userPwd))
.build();
//casting our bookingCreateRequestDTO object to a JSON payload
String jsonBody = new ObjectMapper().writeValueAsString(bookingCreateRequestDTO);
return webClient.post()
.uri(URL)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(jsonBody))
.retrieve()
.bodyToMono(BookingResponse.class)
.timeout(Duration.ofMillis(5000))
.doOnSuccess(response -> {
//Build email template objects
});
}
型
最后是单元测试
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.mockito.BDDMockito.given;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringJUnitConfig
@ContextConfiguration
@ExtendWith(SpringExtension.class)
@AutoConfigureMockMvc
@SpringBootTest(classes = {SecurityConfig.class, BookingControllerTest.TestConfig.class})
class BookingControllerTest {
@Configuration
public static class TestConfig implements WebMvcConfigurer {
@Bean
public HandlerMappingIntrospector mvcHandlerMappingIntrospector() {
return new HandlerMappingIntrospector();
}
// Other MVC configuration methods...
}
@Mock
private BookingService bookingService;
@InjectMocks
private BookingController bookingController;
private MockMvc mockMvc;
@Autowired
private WebApplicationContext context;
@Autowired
private FilterChainProxy springSecurityFilterChain;
@BeforeEach
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(context)
.setControllerAdvice(new GeneralBookingException()) // Add error handling advice
.setControllerAdvice(new UnauthorizedException()) // Add error handling advice
.apply(springSecurity(springSecurityFilterChain))
.build();
}
@Test
public void testUserBooking_Unauthorized() throws Exception {
// Prepare test data
BookingCreateRequestDTO request = new BookingCreateRequestDTO();
request.setClientId("client1");
// Configure the bookingService to throw an exception (Unauthorized)
given(bookingService.processBookingRequest(request))
.willThrow(new UnauthorizedException("Unauthorized"));
// Perform the API call
mockMvc.perform
(post("/booking")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer eyJraWQiOiJDQnk5TFlvM2JUK0M2eVpvcWp3ZzEwTndXXC9GQWxjUURteHVHYWNZdDBhRT0iLCJhbGciOiJSUzI1NiJ9.....")
.content(objectToJson(request))
)
.andExpect(status().isUnauthorized());
}
// Helper method to convert object to JSON string
private String objectToJson(Object object) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(object);
}
}
型
附加说明这里是我的构建文件
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.0'
id 'io.spring.dependency-management' version '1.1.0'
id("org.springdoc.openapi-gradle-plugin") version "1.6.0"
id 'com.google.cloud.tools.jib' version '3.3.1'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation group: 'org.springframework.security', name: 'spring-security-oauth2-client', version: '6.1.0'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation group: 'org.hibernate', name: 'hibernate-validator', version: '8.0.0.Final'
implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.1.0'
implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-api', version: '2.1.0'
implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webflux-ui', version: '2.1.0'
implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webflux-api', version: '2.1.0'
implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-common', version: '2.1.0'
implementation group: 'jakarta.validation', name: 'jakarta.validation-api', version: '3.0.2'
implementation group: 'com.sendgrid', name: 'sendgrid-java', version: '4.9.3'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation group: 'com.google.guava', name: 'guava', version: '32.1.1-jre'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation group: 'org.springframework', name: 'spring-test', version: '6.0.11'
testImplementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation group: 'org.springframework.security', name: 'spring-security-test', version: '6.1.0'
testImplementation 'io.projectreactor:reactor-test'
}
tasks.named('test', Test) {
useJUnitPlatform()
testLogging {
exceptionFormat "full"
events "started", "skipped", "passed", "failed"
showStandardStreams true
}
型
最后是我的Spring安全配置文件:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
String jwkSetUri;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/swagger-ui/index.html",
"/swagger-resources/**",
"/api-docs/swagger-config",
"/api-docs/*",
"/swagger-ui/*",
"/my/docs",
"/error",
"/favicon.ico",
"/*/*.png",
"/*/*.gif",
"/*/*.svg",
"/*/*.jpg",
"/*/*.html",
"/*/*.css").anonymous()
.anyRequest().authenticated()
)
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
// @formatter:on
return http.build();
}
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
}
型
1条答案
按热度按时间unhi4e5o1#
只要使用
MockMvc
,就可以使用SecurityMockMvcRequestPostProcessors.jwt()
中的I contributed tospring-security-test
。要使用我为OAuth2编写的注解,您需要spring-addons-oauth2-test。关于我的Github repo
我写了一篇article on Baeldung,涵盖了使用模拟的OAuth2身份测试Spring组件和应用程序的主题,但是注解最近有了很大的改进,文章还没有更新(Baeldung的编辑过程可能需要一些时间)。也就是说,本文中涉及
SecurityMockMvcRequestPostProcessors
的部分仍然适用。几乎所有我写的回答这个other question是适用于你。只有一个细节:@ScorprocS正在使用“我的”Sping Boot starter(
spring-addons-starter-oidc
)以及spring-boot-starter-oauth2-resource-server
,在我的回答中使用spring-addons-starter-oidc-test
的原因。如果您没有使用spring-addons-starter-oidc
,spring-addons-oauth2-test
(只包含测试注解)就足够了。