REST Assured测试多对一关联实体时Company属性为空问题排查
多对一关联实体REST Assured测试中Company属性接收为null的问题
问题描述
我有一个包含多对一关联的实体,通过Postman手动验证接口功能正常,但使用REST Assured编写单元测试时,尽管在setup中设置了UserEntity的Company属性,API接收请求时该属性却为null。调试显示发送请求前User对象的Company属性已设置,但请求到达POST端点时变为null。
User类
@Getter @Setter @Entity(name = "User") @Table(name = "users") @NamedQueries( @NamedQuery(name = CustomNamedQueries.USER_GET_ALL_IN_COMPANY_DEFINITION, query = CustomNamedQueries.USER_GET_ALL_IN_COMPANY_QUERY) ) public class UserEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", updatable = false, nullable = false) private Long id; @ManyToOne @JsonBackReference @JoinColumn(name = "company_id", referencedColumnName = "id") private CompanyEntity company; }
Company类
@Getter @Setter @Entity(name = "Company") @Table(name = "companies") public class CompanyEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", updatable = false, nullable = false) private Long id; @Email @NotEmpty(message = "A company email is required.") @Column(name = "company_email") private String companyEmail; @JsonManagedReference @OneToMany(mappedBy = "company", fetch = FetchType.EAGER) private List<UserEntity> users; }
测试类
@QuarkusTransactionalTest public class UserControllerIntegrationTest { @Inject CompanyRepository companyRepo; @Inject UserRepository userRepo; private CompanyEntity company; @BeforeEach void setup() { company = saveCompany(); } @AfterEach void cleanup() { userRepo.deleteAll(); companyRepo.deleteAll(); company = null; } @Test void test_getUsersInCompany() { // Given UserEntity user1 = saveFromAPI(); UserEntity user2 = saveFromAPI(); // When List<UserEntity> users = getUsersInACompany(); // Then assertEquals(2, users.size()); assertThat(users.get(0)).isEqualTo(user1); assertThat(users.get(1)).isEqualTo(user2); } private UserEntity saveFromAPI() { UserEntity user = createAdminUser(company); return given() .contentType(ContentType.JSON) .body(user) .post("/api/v1/companies/{companyId}/users", company.getId()) .then() .statusCode(201) .extract() .as(UserEntity.class); } public static UserEntity createUser(CompanyEntity company, boolean isAdmin) { LocalDateTime localDateTime = LocalDateTime.now(); UserEntity user = new UserEntity(); user.setAddress(createAddress()); user.setCompany(company); user.setDateOfBirth(DateTimeFormatter.ofPattern("dd-MM-yyyy", Locale.ENGLISH).format(localDateTime)); user.setEmploymentStartDate(DateTimeFormatter.ofPattern("dd-MM-yyyy", Locale.ENGLISH).format(localDateTime)); user.setEmail(RandomStringUtils.randomAlphabetic(6) + "@mailer.com"); user.setTitle(RandomStringUtils.randomAlphabetic(4)); user.setEmployeeNumber(RandomStringUtils.randomNumeric(5)); user.setJobTitle(RandomStringUtils.randomAlphabetic(8)); user.setStatus(RandomStringUtils.randomAlphabetic(7)); user.setQualification(RandomStringUtils.randomAlphabetic(10)); user.setMobile(RandomStringUtils.randomNumeric(10)); user.setFirstName(RandomStringUtils.randomAlphabetic(10)); user.setLastName(RandomStringUtils.randomAlphabetic(10)); user.setIsAdmin(isAdmin); return user; } }
Postman有效请求体
{ "company": { "id": 1 }, "address": { "number": "14", "street": "Harcourt Street", "city": "Dublin 5", "county": "Dublin", "country": "Ireland", "postcode": "R34 X943" }, "title": "Mr", "firstName": "Bilbo", "lastName": "Baggins", "employeeNumber": "1B345", "jobTitle": "Manager", "status": "Active", "employmentStartDate": "10-04-2000", "dateOfBirth": "21-09-1980", "qualification": "IT Manager", "mobile": "0987654321", "email": "bilbo@baggins.com", "isAdmin": true }
问题原因
核心问题出在@JsonBackReference注解:
- 这个注解的作用是阻止Jackson序列化关联对象,避免Company和User之间的循环引用问题。当测试中把UserEntity对象传入REST Assured的
.body(user)时,Jackson序列化UserEntity时会直接忽略company属性,导致请求体里完全没有company字段,后端自然接收不到。 - Postman是手动构造的JSON,明确包含了
{"company": {"id": 1}},所以后端能正确解析关联关系。
解决方案
方案1:调整序列化注解,保留company字段序列化
把UserEntity中的@JsonBackReference替换为@JsonIgnoreProperties,只忽略Company中的users属性来避免循环引用,同时保留company本身的序列化:
@ManyToOne @JsonIgnoreProperties("users") @JoinColumn(name = "company_id", referencedColumnName = "id") private CompanyEntity company;
方案2:测试时手动构造JSON请求体
不直接传入UserEntity对象,而是手动构造符合Postman格式的JSON数据,确保包含company.id字段:
private UserEntity saveFromAPI() { // 构造请求用的JSON结构 Map<String, Object> userJson = new HashMap<>(); userJson.put("address", createAddress()); // 只传入company的id Map<String, Object> companyJson = new HashMap<>(); companyJson.put("id", company.getId()); userJson.put("company", companyJson); // 填充其他用户字段 LocalDateTime localDateTime = LocalDateTime.now(); userJson.put("dateOfBirth", DateTimeFormatter.ofPattern("dd-MM-yyyy", Locale.ENGLISH).format(localDateTime)); userJson.put("employmentStartDate", DateTimeFormatter.ofPattern("dd-MM-yyyy", Locale.ENGLISH).format(localDateTime)); userJson.put("email", RandomStringUtils.randomAlphabetic(6) + "@mailer.com"); userJson.put("title", RandomStringUtils.randomAlphabetic(4)); userJson.put("employeeNumber", RandomStringUtils.randomNumeric(5)); userJson.put("jobTitle", RandomStringUtils.randomAlphabetic(8)); userJson.put("status", RandomStringUtils.randomAlphabetic(7)); userJson.put("qualification", RandomStringUtils.randomAlphabetic(10)); userJson.put("mobile", RandomStringUtils.randomNumeric(10)); userJson.put("firstName", RandomStringUtils.randomAlphabetic(10)); userJson.put("lastName", RandomStringUtils.randomAlphabetic(10)); userJson.put("isAdmin", true); return given() .contentType(ContentType.JSON) .body(userJson) .post("/api/v1/companies/{companyId}/users", company.getId()) .then() .statusCode(201) .extract() .as(UserEntity.class); }
方案3:使用DTO(数据传输对象)—— 推荐
这是最规范的架构设计,避免直接用实体类作为请求/响应载体:
- 创建UserCreateDTO,只包含前端需要传递的字段:
@Getter @Setter public class UserCreateDTO { private AddressDTO address; private Long companyId; // 仅传递公司ID即可 // 其他用户字段:title、firstName、lastName等... }
- 控制器接收DTO,转换为实体并手动关联Company:
@PostMapping("/api/v1/companies/{companyId}/users") public ResponseEntity<UserEntity> createUser(@PathVariable Long companyId, @RequestBody UserCreateDTO dto) { CompanyEntity company = companyRepo.findById(companyId) .orElseThrow(() -> new RuntimeException("Company not found")); UserEntity user = new UserEntity(); // 复制DTO字段到实体 user.setAddress(dto.getAddress()); user.setCompany(company); user.setFirstName(dto.getFirstName()); // ...其他字段赋值 userRepo.save(user); return ResponseEntity.status(201).body(user); }
- 测试时构造UserCreateDTO传入,既解决序列化问题,又符合分层架构的职责分离原则。
内容的提问来源于stack exchange,提问作者Dan
相关产品推荐
相关产品推荐

