java - Spring websocket测试: repository does not see entity when rollbacked transaction is applied

标签 java spring spring-boot spring-test spring-websocket

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

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

用户.java

@Entity(name = "USERS")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String name;

    // getters,setters ommitted
}

UserRepository.java

@Repository
public interface UserRepository extends JpaRepository<User,Long> {

    User getUserByName(String name);
}

UserService.java

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User getUser(String name){
        return userRepository.getUserByName(name);
    }
}

Websocket 特定

@Configuration
@EnableWebSocket
public class Config implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        webSocketHandlerRegistry.addHandler(userHandler(), "/api/user");
    }

    @Bean
    UserHandler userHandler(){
        return new UserHandler();
    }
}

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);
        }
    }

}

还有我的测试 application.properties:

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
}

然后UserRepository没有找到setUp测试方法中保存的用户。

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

我尝试在其余端点上重现相同的情况,并且它在那里工作。让我们看看。

UserController.java

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping(path = "/api/user")
    public @ResponseBody User getUser(String name){
        return userService.getUser(name);
    }

}

和UserControllerTest.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 为什么这么做?

最佳答案

这本质上是Spring Boot @WebIntegrationTest and TestRestTemplate - Is it possible to rollback test transactions?的重复。 .

I want to see the same behaviour when I test websocket endpoint.

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

这就是为什么如果您另外用 @Transactional 注释您的测试,则会出现两个不同的事务。 :

  1. 测试管理
  2. Spring 管理

对于您的用例,您需要停止使用 @Transactional在您的测试中,并在测试完成后开始手动重置数据库的状态。为此,我建议使用 @SqlAFTER_TEST_METHOD执行阶段。请参阅How to execute @Sql before a @Before method举个例子。

问候,

Sam(Spring TestContext 框架的作者)

关于java - Spring websocket测试: repository does not see entity when rollbacked transaction is applied,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43168413/

相关文章:

java - 找不到符号错误- Swing

java - Maven 原型(prototype) : Validate artifactId or groupId

java - 将当前日期字符串注入(inject) Spring bean 配置中

Windows 按端口号杀死进程

java - Android MVP : What is an Interactor?

java - 与 Java JDBC 作斗争 :sqlite connection using Intellij

java - 为什么 JPA/Hibernate 设计了一种强制重新定义事务边界的方法,即使对于一个插入/更新数据库语句也是如此?

java - 在浏览器中直接点击 post 方法 url 时显示错误消息

java - Flyway/springboot - 配置为在生产/测试上运行,但不在开发上运行

spring-boot - Spring Boot 执行器端点的响应 MIME 类型