【问题标题】:Spring boot test @Transactional not saving春季启动测试@Transactional不保存
【发布时间】:2017-09-25 07:10:21
【问题描述】:

我正在尝试使用 Spring Boot Test 进行简单的集成测试,以测试 e2e 用例。我的测试不起作用,因为我无法让存储库保存数据,我认为我的 spring 上下文有问题...

这是我的实体:

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    @Id
    private int id;
    private String name;
}

这是个人存储库:

@Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {
}

个人服务:

@Service
public class PersonService {

    @Autowired
    private PersonRepository repository;

    public Person createPerson(int id,String name) {
       return repository.save(new Person(id, name));
    }

    public List<Person> getPersons() {
      return repository.findAll();
    }
}

个人控制器:

@RequestMapping
@RestController
public class PersonController {

  @Autowired
  private PersonService personService;

  @RequestMapping("/persons")
  public List<Person> getPersons() {
      return personService.getPersons();
  }

}

主要应用类:

@SpringBootApplication
public class BootIntegrationTestApplication {

  public static void main(String[] args) {
    SpringApplication.run(BootIntegrationTestApplication.class, args);
  }
}

application.properties 文件:

spring.datasource.url= jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true

还有测试:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BootIntegrationTestApplicationTests {

    @Autowired
    private PersonService personService;
    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    @Transactional
    public void contextLoads() {
        Person person = personService.createPerson(1, "person1");
        Assert.assertNotNull(person);

        ResponseEntity<Person[]> persons = restTemplate.getForEntity("/persons", Person[].class);
    }
}

测试不起作用,因为服务没有保存 Person 实体 .... 在此先感谢

【问题讨论】:

  • 当然不是...您的测试是事务性的,因此事务包装了您的测试方法。所以它是之后回滚(并且您的服务参与该事务)。即使你会flush 它也不会工作,因为你正在使用 rest 来访问将打开一个新事务的应用程序。完成这项工作的唯一方法是使测试不是事务性的。
  • 您可以尝试拆分测试。创建私有方法保存带有事务注释的人并从测试方法中调用它。
  • 正如@M.Deinum 所说,事务在您的测试中回滚,您看不到任何变化。尝试将 '@Commit' 或 @Rollback(false) 添加到您的测试中。
  • 这也行不通,因为涉及 2 个事务,并且第一个事务在他调用其余服务之前尚未提交。后者将启动另一个事务,但由于另一个事务仍未提交,因此它看不到该数据(记住 ACID 中的 I 用于隔离)

标签: java spring spring-boot spring-test


【解决方案1】:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class SmokeTest {

    @Autowired
    UserController userController;

    @Autowired
    UserDao userDAO;

    @Rollback(false) // This is key to avoid rollback.
    @Test   
    public void contextLoads() throws Exception {
        System.out.println("Hiren");

        System.out.println("started");
        userDAO.save(new User("tyx", "x@x.com"));
    }
}

参考@Rollback(false) 是避免回滚的关键。

【讨论】:

  • 自从 spring 4.2 wa 可以使用 @Commit 代替
【解决方案2】:

感谢 M. Deinum,我想我明白了, 所以最好的办法是将测试的逻辑分成两个测试,第一个只测试服务(所以这个可以是事务性的),第二个是控制器:

测试 1:

@Test
@Transactional
public void testServiceSaveAndRead() {
    personService.createPerson(1, "person1");
    Assert.assertTrue(personService.getPersons().size() == 1);
}

测试 2:

@MockBean
private PersonService personService;

@Before
public void setUp() {
    //mock the service
    given(personService.getPersons())
            .willReturn(Collections.singletonList(new Person(1, "p1")));
}

@Test
public void testController() {
    ResponseEntity<Person[]> persons = restTemplate.getForEntity("/persons", Person[].class);
    Assert.assertTrue(persons.getBody()!=null && persons.getBody().length == 1);
}

【讨论】:

    【解决方案3】:

    用于保存实体的 Spring 需要事务。但是在事务提交之前,从另一个事务中看不到更改。

    最简单的方法是在提交事务后调用控制器

    @Test
    @Transactional
    public void contextLoads() {
        Person person = personService.createPerson(1, "person1");
        Assert.assertNotNull(person);
    
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                ResponseEntity<Person[]> persons = restTemplate.getForEntity("/persons", Person[].class);
            }
        });        
    }
    

    【讨论】:

    • 完全正确!正如其他答案所暗示的那样,测试不应永久改变数据库。
    • 我认为重要的是要注意使用这种方法,我无法在afterCommit 方法中调试代码。我正在使用 Inellij,调试器不会进入那里。因此,如果您需要能够调试您的测试,这是行不通的。
    • @JCB afterCommit 仅在事务内部工作
    【解决方案4】:

    对于每个进行数据库事务的@Test 函数,如果您想永久保留更改,则可以使用@Rollback(false)

    @Rollback(false)
    @Test
    public void createPerson() throws Exception {
        int databaseSizeBeforeCreate = personRepository.findAll().size();
    
        // Create the Person
        restPersonMockMvc.perform(post("/api/people")
            .contentType(TestUtil.APPLICATION_JSON_UTF8)
            .content(TestUtil.convertObjectToJsonBytes(person)))
            .andExpect(status().isCreated());
    
        // Validate the Person in the database
        List<Person> personList = personRepository.findAll();
        assertThat(personList).hasSize(databaseSizeBeforeCreate + 1);
        Person testPerson = personList.get(personList.size() - 1);
        assertThat(testPerson.getFirstName()).isEqualTo(DEFAULT_FIRST_NAME);
        assertThat(testPerson.getLastName()).isEqualTo(DEFAULT_LAST_NAME);
        assertThat(testPerson.getAge()).isEqualTo(DEFAULT_AGE);
        assertThat(testPerson.getCity()).isEqualTo(DEFAULT_CITY);
    }
    

    我用jHipster生成的SpringBoot项目测试了一下:

    • SpringBoot:1.5.4
    • jUnit 4.12
    • 春季 4.3.9

    【讨论】:

      【解决方案5】:

      不要使用@Rollback(false)。单元测试不应生成数据。

      JPA FlushMode 是 AUTO(默认 - 查询发生时刷新 INSERT/UPDATE/DELETE SQL)/COMMIT。

      只查询工作实体强制刷新,或者使用EntityManager强制刷新

      @Test
      public void testCreate(){
          InvoiceRange range = service.createInvoiceRange(1, InvoiceRangeCreate.builder()
                  .form("01GTKT0/010")
                  .serial("NV/18E")
                  .effectiveDate(LocalDate.now())
                  .rangeFrom(1L)
                  .rangeTo(1000L)
                  .build(), new byte[] {1,2,3,4,5});
      
          service.findByCriteria(1, "01GTKT0/010", "NV/18E");  // force flush
          // em.flush(); // another way is using entityManager for force flush
      }
      

      【讨论】:

        【解决方案6】:

        注意测试的执行顺序,带有@Commit或@Rollback(false)注解的测试必须先执行:https://www.baeldung.com/junit-5-test-order

        【讨论】:

          猜你喜欢
          • 2015-09-18
          • 2020-02-02
          • 2015-01-22
          • 1970-01-01
          • 2016-12-13
          • 2018-01-21
          • 2019-03-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多