Spring boot测试@Transactional不保存

问题描述 投票:0回答:7

我正在尝试使用 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 实体.... 预先感谢

java spring spring-boot spring-test
7个回答
20
投票
@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", "[email protected]"));
    }
}

参考

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


7
投票

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

5
投票

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

1
投票

对于每个进行数据库事务的

@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
  • j单元 4.12
  • 春季4.3.9

1
投票

请勿使用

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

JPA FlushMode 为

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

只需查询工作实体强制FLUSH,或者使用EntityManager强制flush

@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
}

1
投票

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


0
投票

我没有在官方文档中看到任何提及此细节的内容:

如果你的测试是@Transactional,它默认会在每个测试方法结束时回滚事务。然而,由于使用这种安排与 RANDOM_PORT 或 DEFINED_PORT 隐式地提供了真正的 servlet 环境,HTTP 客户端和服务器在单独的线程中运行,因此在单独的事务中运行。在这种情况下,在服务器上发起的任何事务都不会回滚。

换句话说,当您在测试中使用

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
时,@Transactional 将无法按预期工作。

© www.soinside.com 2019 - 2024. All rights reserved.