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

JUnit测试中ImageUtil.scaleImage方法始终返回null问题求助

问题:ImageUtil工具类scaleImage方法JUnit测试返回null排查

我有一个带final私有构造方法的工具类ImageUtil,所有方法均为静态方法。在执行JUnit测试调用其scaleImage方法时,该方法始终返回null。以下是相关代码及堆栈跟踪信息,恳请协助排查解决。

工具类代码

public final class ImageUtil {
    final static Logger logger = LoggerFactory.getLogger(ImageUtil.class);

    private ImageUtil() {}

    private static final int MAX_MASTER_WIDTH = 2000;
    private static final int MIN_MASTER_WIDTH = 500;
    private static final int MAX_SAMPLE_WIDTH = 400;

    public static BufferedImage scaleImage(BufferedImage image, int maxWidth, int type){
        int oldWidth = image.getWidth();
        int oldHeight = image.getHeight();
        int newHeight = (int) Math.round((double) maxWidth / (double) oldWidth * (double) oldHeight);

        BufferedImage resized = new BufferedImage(maxWidth, newHeight, type);
        Graphics2D g = resized.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(image, 0, 0, maxWidth, newHeight, 0, 0, image.getWidth(), image.getHeight(), null);
        g.dispose();

        return resized;
    }
}

测试类代码

@PrepareForTest(value = {FileUtil.class,JpegReader.class,ImageUtil.class,System.class,FileUtils.class,JpegReader.class,ImageIO.class})
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)

class ImageUtilTest {

    MockedStatic<ImageUtil> imageUtil= null ;
    File tempFile = null;

    @ClassRule
    TemporaryFolder temporaryFolder ;
    
    @BeforeEach
    public void init() throws IOException {
        imageUtil= mockStatic(ImageUtil.class);
        tempFile = mock(File.class);
        temporaryFolder = new TemporaryFolder();
        temporaryFolder.create();
    }

    @AfterEach
    public void afterEach() {
        imageUtil.close();
        tempFile.delete();
        temporaryFolder.delete();
    }
    
    @Test
    @DisplayName("scaleImageTest")
    public  void scaleImageTest() throws Exception{
        int oldWidth = 200;
        int oldHeight = 150;
        int newHeight = (int) Math.round((double) 200 / (double) oldWidth * (double) oldHeight);

        BufferedImage mockedImage = mock(BufferedImage.class) ;
        BufferedImage mockedNewImage = mock(BufferedImage.class) ;
        Graphics2D mockedGraphics = mock(Graphics2D.class);
        
        when(mockedImage.getWidth()).thenReturn(oldWidth);
        when(mockedImage.getHeight()).thenReturn(oldHeight);
        when(mockedImage.getWidth()).thenReturn(200);
        
        Mockito.doNothing().when(mockedGraphics).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        when(mockedGraphics.drawImage(mockedImage, 0, 0, 200, newHeight, 0, 0, oldHeight,oldWidth, null)).thenReturn(true);
        Mockito.doNothing().when(mockedGraphics).dispose();

        PowerMockito.whenNew(BufferedImage.class).withArguments(oldWidth, oldHeight,newHeight).thenReturn(mockedNewImage);
        when(mockedNewImage.createGraphics()).thenReturn(mockedGraphics);
        
        BufferedImage actualImage = ImageUtil.scaleImage(mockedImage, oldWidth, newHeight);
        assertEquals(mockedNewImage, actualImage);
        
    }
}

堆栈跟踪信息

org.opentest4j.AssertionFailedError: expected: <Mock for BufferedImage, hashCode: 307036850> but was: <null>
    at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
    at org.junit.jupiter.api.AssertionUtils.failNotEqual(AssertionUtils.java:62)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
    at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1141)
    at test.com.renoworks.framework.util.ImageUtilTest.scaleImageTest(ImageUtilTest.java:234)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)

问题原因及修复方案

核心问题

  1. 错误mock了目标工具类:测试中调用mockStatic(ImageUtil.class),这会让所有静态方法调用默认返回null,而不是执行真实的方法逻辑。我们要测试scaleImage的真实实现,不需要mock整个类。
  2. whenNew参数不匹配:工具类中创建BufferedImage的代码是new BufferedImage(maxWidth, newHeight, type),但测试中用withArguments(oldWidth, oldHeight,newHeight),参数顺序和值都不对,导致无法返回预期的mock对象。
  3. drawImage参数顺序错误:工具类中调用drawImage时,源宽高是image.getWidth(), image.getHeight(),但测试中写的是oldHeight,oldWidth,顺序颠倒,导致mock匹配失败。
  4. 参数类型错误:原测试中调用scaleImage时第三个参数传了newHeight,但该参数应该是BufferedImage的类型常量(如TYPE_INT_RGB),而非高度值。

修正后的测试代码

@PrepareForTest(ImageUtil.class)
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class ImageUtilTest {

    @TempDir
    TemporaryFolder temporaryFolder;

    @Test
    @DisplayName("scaleImageTest")
    public void scaleImageTest() throws Exception {
        int oldWidth = 200;
        int oldHeight = 150;
        int targetMaxWidth = oldWidth;
        int imageType = BufferedImage.TYPE_INT_RGB;
        int newHeight = (int) Math.round((double) targetMaxWidth / (double) oldWidth * (double) oldHeight);

        // Mock依赖对象
        BufferedImage mockedImage = mock(BufferedImage.class);
        BufferedImage mockedNewImage = mock(BufferedImage.class);
        Graphics2D mockedGraphics = mock(Graphics2D.class);

        // 设置mock行为
        when(mockedImage.getWidth()).thenReturn(oldWidth);
        when(mockedImage.getHeight()).thenReturn(oldHeight);
        when(mockedNewImage.createGraphics()).thenReturn(mockedGraphics);

        // 匹配BufferedImage的构造参数
        PowerMockito.whenNew(BufferedImage.class)
                .withArguments(targetMaxWidth, newHeight, imageType)
                .thenReturn(mockedNewImage);

        // 匹配drawImage的参数顺序
        doNothing().when(mockedGraphics).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        when(mockedGraphics.drawImage(mockedImage, 0, 0, targetMaxWidth, newHeight, 0, 0, oldWidth, oldHeight, null))
                .thenReturn(true);
        doNothing().when(mockedGraphics).dispose();

        // 调用真实方法
        BufferedImage actualImage = ImageUtil.scaleImage(mockedImage, targetMaxWidth, imageType);

        // 断言结果
        assertEquals(mockedNewImage, actualImage);
    }
}

额外优化点

  • 使用JUnit 5的@TempDir替代@ClassRule的TemporaryFolder,更符合JUnit 5规范。
  • 移除了不必要的mockStatic(ImageUtil.class)和无关的File mock,聚焦测试目标方法。
  • 明确imageType参数,避免用高度值代替类型常量的错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 23:10:43