我有以下测试,该测试生成带有公司ID和电子邮件的邀请。然后,我们使用相同的公司ID和电子邮件生成第二个。这将违反数据库上的唯一约束(请参见邀请实体)。事实是,该测试通过了以下列表(我猜该交易永远不会提交第二次插入)
如果我在第二次插入之后执行findAll()
(请参阅下面的注释),则在findAll()
发生索引冲突的行上会出现异常。
当方法返回时,如何使UserService
中的方法落实其事务,因此测试代码将按原样抛出异常?
@RunWith(SpringRunner.class)
@DataJpaTest
public class UserServiceTest {
... Everything is autowired
@Test
public void testCreateInvitation() {
String email = "test2@example.com";
Company company = userService.createCompany("ACMETEST", tu);
assertTrue(invitationRepository.count() == 0l);
assertNotNull(userService.sendInvitation(company, email));
assertTrue(invitationRepository.count() == 1l);
assertTrue(invitationRepository.findAllByEmail(email).size() == 1);
// Second should throw exception
userService.sendInvitation(company, email);
// If this line is uncommented, I get key violation exception, otherwise, test passes!!!!????
//Iterable<Invitation> all = invitationRepository.findAll();
}
邀请类别和约束:
@Entity
@Table(uniqueConstraints=@UniqueConstraint(columnNames={"email", "company_id"}))
@JsonIdentityInfo(generator= ObjectIdGenerators.PropertyGenerator.class, property="id")
@Data
public class Invitation {
@Id
@GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid")
String id;
@ManyToOne
@JoinColumn(name="company_id")
private Company company;
String email;
Date inviteDate;
Date registerDate;
}
用户服务:
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class UserService {
public Invitation sendInvitation(Company company, String toEmail) {
Invitation invitation = new Invitation();
invitation.setCompany(company);
invitation.setInviteDate(new Date());
invitation.setEmail(toEmail);
try {
// TODO send email
return invitationRepository.save(invitation);
} catch (Exception e) {
LOGGER.error("Cannot send invitation: {}", e.getMessage());
}
return null;
}
}
答案 0 :(得分:2)
当方法返回时,如何使UserService中的方法提交事务,所以测试代码将按原样抛出异常?
由于@DataJpaTest
本身就是@Transactional
。用@Test
方法编写的代码在事务内执行。您可以使用Propogation.NOT_SUPPORTED更改它
以非事务方式执行,如果存在则暂停当前事务
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testCreateInvitation() {
// your test method body unchanged
}
使用此方法,您应该在第二次userService.sendInvitation(company, email);
调用中获得异常。
还有其他一些选择。
选项1:
将@Commit
添加到您的测试方法中。
所有标记为@Transactional
的测试在执行后均为rolled back。但是Hibernate有一个微妙的问题:它可能会延迟sql的执行,直到您提交事务(或显式调用flush
(选项2))。
由于事务已回滚,所以不会执行插入操作,因此不会违反约束。
请注意,使用@Commit
会在测试方法的外部之外引发异常。
选项2:
使用手册flush()
:
如果您的存储库extends JpaRepository<>
:
@Test
public void testCreateInvitation() {
// create first invitation,
// create second invitation
invitationRepository.flush();
}
否则注入EntityManager
并对其调用flush()
@PersistenceContext
private EntityManager em;
@Test
public void testCreateInvitation() {
// create first invitation,
// create second invitation
em.flush();
}
调用invitationRepository.flush()
或em.flush()
时会出现异常。
选项3: 使用GenerationType.IDENTITY:
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
休眠@Entity
之后,Hibernate必须为save()
分配ID。但是,如果不执行insert
,就无法获取实体的ID。
PS 有一件事情让我感到困惑。
您使用@Transactional(propagation = Propagation.REQUIRES_NEW)
,它应在执行该方法后强制执行事务。请检查您的日志,以查看事务是否确实有效。