Spring websocket 测试:应用回滚事务时存储库看不到实体



我在测试 websocket 端点时遇到了这种情况,当我使用回滚事务回滚测试期间所做的所有更改时,我遇到了不可预测的行为。

我创建了一个简单的例子来向我们展示正在发生的事情。

用户.java

@Entity(name = "USERS")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
// getters,setters ommitted
}

用户存储库.java

@Repository
public interface UserRepository extends JpaRepository<User,Long> {
User getUserByName(String name);
}

用户服务.java

@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUser(String name){
return userRepository.getUserByName(name);
}
}

特定于网络套接字

@Configuration
@EnableWebSocket
public class Config implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(userHandler(), "/api/user");
}
@Bean
UserHandler userHandler(){
return new UserHandler();
}
}

用户处理程序.java

public class UserHandler extends TextWebSocketHandler{
@Autowired
private UserService userService;
@Override
public void afterConnectionEstablished(WebSocketSession session) {
try {
HttpHeaders headers = session.getHandshakeHeaders();
List<String> userNameHeader = headers.get("user_name");
if (userNameHeader == null || userNameHeader.isEmpty()){
session.sendMessage(new TextMessage("user header not found"));
return;
}
String userName = userNameHeader.get(0);
User user = userService.getUser(userName);
if (user == null){
session.sendMessage(new TextMessage("user not found"));
return;
}
session.sendMessage(new TextMessage("ok"));
} catch (Throwable e) {
e.printStackTrace();
}
}
}

最终是UserHandlerTest.java。我使用 okHttp3 作为 websocket 测试客户端:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class UserHandlerTest {
@Autowired
private UserRepository userRepository;
private User testUser;
private BlockingQueue<String> messages = new LinkedBlockingQueue<>();
private OkHttpClient okHttpClient;
@Before
public void setUp(){
okHttpClient = new OkHttpClient();
testUser = new User();
testUser.setName("test");
userRepository.save(testUser);
}
@Test
public void testThatUserExist(){
Request request = new Request.Builder()
.url("ws://127.0.0.1:8080/api/user")
.header("user_name","test")
.build();
WebSocket ws = okHttpClient.newWebSocket(request,new MsgListener());
String msg = null;
try {
msg = messages.poll(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
System.out.println(e);
}
Assert.assertNotNull(msg);
Assert.assertThat(msg,is("ok"));
}

private class MsgListener extends WebSocketListener{
@Override
public void onOpen(WebSocket webSocket, Response response) {
System.out.println("onOpen:"+response.message());
}
@Override
public void onMessage(WebSocket webSocket, String text) {
System.out.println("onMessage:"+text);
messages.add(text);
}
}
}

和我的测试应用程序属性:

spring.jpa.database=postgresql
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.username=postgres
spring.datasource.password=******
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/USERS_TEST

上面的所有代码都没问题,测试通过了。但是假设我不想弄乱数据库,所以我在所有测试方法上使用@Transactional在测试完成后回滚所有更改。

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@Transactional
public class UserHandlerTest {
// the same as above
}

然后用户存储库找不到保存在设置测试方法中的用户。

java.lang.AssertionError: 
Expected: is "ok"
but: was "user not found"

我试图在其余端点上重现相同的情况,它在那里工作。我看看。

用户控制器.java

@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping(path = "/api/user")
public @ResponseBody User getUser(String name){
return userService.getUser(name);
}
}

和用户控制器测试.java:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@Transactional
public class UserControllerTest {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@Autowired
private UserRepository userRepository;
private User testUser;
@Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
testUser = new User();
testUser.setName("test");
userRepository.save(testUser);
}
@Test
public void testThatUserExist() throws Exception {
mockMvc.perform(get("/api/user")
.param("name","test")
).andExpect(status().isOk())
.andExpect(jsonPath("name").value(is("test")));
}
}

最后一个测试通过,事务回滚。

我希望在测试 websocket 端点时看到相同的行为。

有人可以指出我这里的区别在哪里吗?为什么春天会这样做?

这本质上是Spring Boot @WebIntegrationTest和TestRestTemplate的副本 - 是否可以回滚测试事务?

我希望在测试 websocket 端点时看到相同的行为。

你不能。当你声明@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)时,Spring Boot 会启动一个嵌入式的 Servlet 容器。

这就是为什么如果您另外用@Transactional注释测试,则有两种不同的事务:

  1. 测试管理
  2. 弹簧管理

对于您的使用案例,您需要停止在测试中使用@Transactional,并在测试完成后开始手动重置数据库的状态。为此,我建议在AFTER_TEST_METHOD执行阶段使用@Sql。有关示例,请参阅如何在@Before方法之前执行@Sql。

问候

Sam(Spring TestContext Framework的作者)

最新更新