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

如何用Mockito为EncoderService的createNewStream方法编写单元测试

How to Write Unit Tests for EncoderService's createNewStream with Mockito

Alright, let's break down how to test this method properly. The core challenge here is isolating the RestTemplate HTTP call—we don't want our unit tests making real network requests, so Mockito will help us mock that dependency. Let's start with best practices, then cover edge cases.

Your current method creates a RestTemplate instance directly with new RestTemplate(). That's a problem because we can't mock an object that's created inside the method. Let's fix that by injecting RestTemplate as a dependency instead:

@Service
public class EncoderService {
    // Inject RestTemplate via constructor (constructor injection is preferred for testability)
    private final RestTemplate restTemplate;

    // Keep your existing constants
    private static final String IP = "...";
    private static final String VERSION = "...";
    private static final String serverName = "...";
    private static final String VHOSTS = "...";
    private static final String vhostName = "...";
    private static final String APP_NAME = "...";
    private static final String appName = "...";
    private static final String STREAM_FILES = "...";
    private static final String STREAMS = "...";
    private static final String MESSAGE = "...";
    private static final String UTF_8 = "...";

    public EncoderService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public ResponseEntity<Object> createNewStream(Long channelId) {
        // Your existing logic here, replace new RestTemplate() with this.restTemplate
        String url = IP + VERSION + serverName + VHOSTS + vhostName + APP_NAME + appName + STREAM_FILES;
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON_UTF8));
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        headers.setAcceptCharset(Arrays.asList(Charset.forName(UTF_8)));
        
        String udp = "udp://localhost:1935";
        Map<String, String> map = new HashMap<>();
        map.put("name", STREAMS + appName + channelId);
        map.put("serverName", serverName);
        map.put("uri", udp);
        HttpEntity<Map<String, String>> request = new HttpEntity<>(map, headers);

        try {
            ResponseEntity<Object> response = restTemplate.postForEntity(url, request, Object.class);
            map.put(MESSAGE, "successful");
            return new ResponseEntity<>(map, response.getStatusCode());
        } catch (HttpStatusCodeException e) {
            map.put(MESSAGE, e.getMessage());
            return new ResponseEntity<>(map, HttpStatus.BAD_REQUEST);
        }
    }
}

This small refactor makes the class infinitely more testable. If you can't modify the original code for some reason, we'll cover a PowerMock workaround later.

Write the Unit Tests (Mockito + JUnit 5)

First, make sure you have the right dependencies in your build file:

Maven Dependencies

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.9.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.9.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.11.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>4.11.0</version>
    <scope>test</scope>
</dependency>

Test Class Code

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class EncoderServiceTest {

    // Mock the RestTemplate dependency
    @Mock
    private RestTemplate restTemplate;

    // Inject the mocked RestTemplate into our EncoderService instance
    @InjectMocks
    private EncoderService encoderService;

    // If your constants are private static, use reflection to set test values in a @BeforeEach method
    // Example:
    /*
    @BeforeEach
    void setUp() throws NoSuchFieldException, IllegalAccessException {
        setStaticConstant(EncoderService.class, "IP", "http://test-api.example.com/");
        setStaticConstant(EncoderService.class, "VERSION", "v1/");
        setStaticConstant(EncoderService.class, "serverName", "test-server");
        // Repeat for all other constants to match your test scenario
    }

    private void setStaticConstant(Class<?> clazz, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
        var field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        // Remove final modifier if needed
        var modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
        field.set(null, value);
    }
    */

    @Test
    void createNewStream_WhenHttpRequestSucceeds_ReturnsSuccessResponse() {
        // Test data
        Long channelId = 1L;
        String expectedUrl = "http://test-api.example.com/v1/test-server/vhosts/test-vhost/app/test-app/stream_files"; // Match your constant values
        HttpStatus expectedStatus = HttpStatus.CREATED;
        
        // Mock the RestTemplate response
        ResponseEntity<Object> mockApiResponse = new ResponseEntity<>(Map.of(), expectedStatus);
        when(restTemplate.postForEntity(eq(expectedUrl), any(HttpEntity.class), eq(Object.class)))
                .thenReturn(mockApiResponse);

        // Execute the method under test
        ResponseEntity<Object> result = encoderService.createNewStream(channelId);

        // Verify the result
        assertEquals(expectedStatus, result.getStatusCode());
        Map<String, String> responseBody = (Map<String, String>) result.getBody();
        assertNotNull(responseBody);
        assertEquals("successful", responseBody.get("message")); // Match your MESSAGE constant
        assertEquals("streams-test-app1", responseBody.get("name")); // Match STREAMS + appName + channelId
        assertEquals("test-server", responseBody.get("serverName"));
        assertEquals("udp://localhost:1935", responseBody.get("uri"));

        // Verify RestTemplate was called exactly once with the right parameters
        verify(restTemplate, times(1))
                .postForEntity(eq(expectedUrl), any(HttpEntity.class), eq(Object.class));
    }

    @Test
    void createNewStream_WhenHttpRequestFails_ReturnsBadRequestWithError() {
        // Test data
        Long channelId = 2L;
        String expectedUrl = "http://test-api.example.com/v1/test-server/vhosts/test-vhost/app/test-app/stream_files";
        String errorMessage = "400 Bad Request: Invalid channel ID";
        
        // Mock the exception
        HttpStatusCodeException mockException = mock(HttpStatusCodeException.class);
        when(mockException.getMessage()).thenReturn(errorMessage);
        when(restTemplate.postForEntity(eq(expectedUrl), any(HttpEntity.class), eq(Object.class)))
                .thenThrow(mockException);

        // Execute the method
        ResponseEntity<Object> result = encoderService.createNewStream(channelId);

        // Verify the error response
        assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode());
        Map<String, String> responseBody = (Map<String, String>) result.getBody();
        assertNotNull(responseBody);
        assertEquals(errorMessage, responseBody.get("message"));

        // Verify the exception was triggered
        verify(restTemplate, times(1))
                .postForEntity(eq(expectedUrl), any(HttpEntity.class), eq(Object.class));
    }
}

If You Can't Refactor the Original Code (PowerMock Workaround)

If you're stuck with new RestTemplate() inside the method, you'll need PowerMock to intercept the object creation. Here's how:

Add PowerMock Dependencies

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>

PowerMock Test Class

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;

import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

@RunWith(PowerMockRunner.class)
@PrepareForTest(EncoderService.class) // Tell PowerMock to prepare this class for bytecode manipulation
public class EncoderServicePowerMockTest {

    @Test
    public void createNewStream_SuccessScenario() throws Exception {
        // Mock RestTemplate instance
        RestTemplate mockRestTemplate = mock(RestTemplate.class);
        // Intercept the new RestTemplate() call in EncoderService
        PowerMockito.whenNew(RestTemplate.class).withNoArguments().thenReturn(mockRestTemplate);

        // Set static constants via reflection (same as before)
        // ...

        // Mock the API response
        Long channelId = 1L;
        String expectedUrl = "http://test-api.example.com/v1/test-server/vhosts/test-vhost/app/test-app/stream_files";
        ResponseEntity<Object> mockResponse = new ResponseEntity<>(Map.of(), HttpStatus.CREATED);
        when(mockRestTemplate.postForEntity(eq(expectedUrl), any(HttpEntity.class), eq(Object.class)))
                .thenReturn(mockResponse);

        // Create the service instance
        EncoderService encoderService = new EncoderService();

        // Execute the method
        ResponseEntity<Object> result = encoderService.createNewStream(channelId);

        // Verify results
        assertEquals(HttpStatus.CREATED, result.getStatusCode());
        assertEquals("successful", ((Map<String, String>) result.getBody()).get("message"));

        // Verify the new RestTemplate() was called once
        PowerMockito.verifyNew(RestTemplate.class).withNoArguments();
        verify(mockRestTemplate, times(1))
                .postForEntity(eq(expectedUrl), any(HttpEntity.class), eq(Object.class));
    }

    // The error scenario test would follow the same pattern as the Mockito version
}

Key Takeaways

  • Dependency Injection is King: Always inject external dependencies like RestTemplate instead of creating them inside methods—it makes testing trivial.
  • Mock External Calls: Never let unit tests hit real APIs. Mock RestTemplate to control responses and verify interactions.
  • Cover All Code Paths: Test both success and failure scenarios to ensure your exception handling works as expected.
  • Handle Static Constants: Use reflection to set test values for private static constants if needed.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:28:36