You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何对HATEOAS及返回HATEOAS的控制器进行单元测试?Spring Boot中RepresentationModelAssembler测试与MockMvc JSON结构差异问题咨询

Hey there! Let's tackle your Spring HATEOAS testing questions step by step—they're common pain points, so I'm glad you asked.

1. Unit Testing HATEOAS Controllers & Core HATEOAS Behavior

When testing controllers that return HATEOAS responses, you’ll want to verify two key things: the business data is correct, and the hypermedia links are properly generated and formatted.

  • Validate Response Structure with MockMvc
    Use MockMvc to send requests and assert on the JSON output, including the presence and correctness of links. For example:

    @Test
    void getUser_ReturnsHalFormattedResponse() throws Exception {
        mockMvc.perform(get("/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.id").value(1))
               // Verify self link exists and has the right URL
               .andExpect(jsonPath("$._links.self.href").value("http://localhost/users/1"))
               // Verify a related resource link
               .andExpect(jsonPath("$._links.orders.href").value("http://localhost/users/1/orders"));
    }
    
  • Test Link Generation in Isolation
    If your link logic is complex (e.g., using WebMvcLinkBuilder for dynamic URLs), test it separately without spinning up the entire controller. This keeps tests fast and focused:

    @Test
    void generateSelfLink_ForUser_ReturnsCorrectUrl() {
        User user = new User(1L, "Jane Smith");
        Link selfLink = WebMvcLinkBuilder.linkTo(UserController.class)
                                         .slash(user.getId())
                                         .withSelfRel();
        
        assertEquals("http://localhost/users/1", selfLink.getHref());
        assertEquals(IanaLinkRelations.SELF, selfLink.getRel());
    }
    

2. Unit Testing RepresentationModelAssembler Implementations

This is straightforward because RepresentationModelAssembler classes are plain Java components—no need for Spring context in unit tests. You can instantiate them directly (or use Mockito for dependencies) and verify their output.

Let’s say you have an assembler like this:

@Component
public class UserModelAssembler implements RepresentationModelAssembler<User, UserModel> {
    @Override
    public UserModel toModel(User user) {
        UserModel model = UserModel.of(user);
        model.add(linkTo(UserController.class).slash(user.getId()).withSelfRel());
        model.add(linkTo(methodOn(UserController.class).getUserOrders(user.getId())).withRel("orders"));
        return model;
    }
}

Here’s how to test it:

class UserModelAssemblerTest {
    // No Spring needed—just instantiate the assembler
    private final UserModelAssembler assembler = new UserModelAssembler();

    @Test
    void toModel_AddsCorrectLinksToUserModel() {
        // Given
        User testUser = new User(1L, "John Doe");

        // When
        UserModel resultModel = assembler.toModel(testUser);

        // Then
        assertNotNull(resultModel);
        assertEquals(testUser.getId(), resultModel.getId());

        // Validate self link
        Link selfLink = resultModel.getLink(IanaLinkRelations.SELF)
                                  .orElseThrow(() -> new AssertionError("Self link missing"));
        assertEquals("/users/1", selfLink.getHref());
        assertEquals(IanaLinkRelations.SELF, selfLink.getRel());

        // Validate orders link
        Link ordersLink = resultModel.getLink("orders")
                                    .orElseThrow(() -> new AssertionError("Orders link missing"));
        assertEquals("/users/1/orders", ordersLink.getHref());
        assertEquals("orders", ordersLink.getRel());
    }
}

If your assembler has dependencies (e.g., a service to fetch related data), use Mockito to inject mocks:

class UserModelAssemblerTest {
    @Mock
    private UserOrderService orderService;

    @InjectMocks
    private UserModelAssembler assembler;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    // Test methods can now mock orderService behavior as needed
}
Why StandaloneSetup vs @SpringBootTest Produces Different JSON Outputs

The difference you’re seeing comes down to how Spring HATEOAS handles JSON serialization:

Root Cause

  • @SpringBootTest loads the full Spring context, including Spring HATEOAS’s auto-configured HypermediaMappingJackson2HttpMessageConverter. This converter uses the HAL format, which serializes links to _links and embedded content to _embedded.
  • StandaloneSetup (by default) uses Jackson’s default message converter, which serializes the RepresentationModel class’s raw links field to a links JSON key, and content (for paged/collections) to a content key—no HAL formatting.

Fixes to Match Production Output

Option 1: Add the HATEOAS Message Converter to StandaloneSetup

Manually configure MockMvc to use Spring HATEOAS’s converter:

@BeforeEach
void setUp() {
    UserController controller = new UserController(new UserModelAssembler());

    // Create the HAL-compatible message converter
    HypermediaMappingJackson2HttpMessageConverter halConverter = 
        new HypermediaMappingJackson2HttpMessageConverter();
    halConverter.setObjectMapper(new ObjectMapper().registerModule(new Jackson2HalModule()));

    mockMvc = MockMvcBuilders.standaloneSetup(controller)
                             .setMessageConverters(halConverter)
                             .build();
}

Option 2: Use @WebMvcTest Instead of StandaloneSetup

@WebMvcTest loads a slimmed-down web context that includes Spring HATEOAS’s auto-configuration, so MockMvc will automatically use HAL formatting. This is the simpler approach:

@WebMvcTest(UserController.class)
class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserModelAssembler assembler;

    @Test
    void getUser_ReturnsHalFormattedResponse() throws Exception {
        User testUser = new User(1L, "John Doe");
        UserModel model = UserModel.of(testUser);
        model.add(Link.of("/users/1", IanaLinkRelations.SELF));
        
        when(assembler.toModel(testUser)).thenReturn(model);

        mockMvc.perform(get("/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$._links.self.href").value("/users/1"));
    }
}

内容的提问来源于stack exchange,提问作者AceCrow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 15:43:11