使用 mockito 验证是否已记录带有 lambda 参数的错误

Verify with mockito that error with lambda arguments has been logged

我正在使用 log4j 作为记录器,并希望使用 lambda 作为日志消息的参数,以便在未启用特定日志级别的情况下延迟对消息的评估:

    protected void appendAndSend(PcapPacket packet) {
        ...
        if (packet.getWireLength() > snapShotLength) {
            logger.error("{}: Received a packet with wire length {} larger than configured snapshot length {}", 
                () -> LOGGING_FILTER_TCP_ERROR, 
                () -> packet.getWireLength(), 
                () -> snapShotLength);
            return;
        }
        ...
    }

在我的测试中,我想检查一条消息是否已写入错误日志

    @Mock
    Logger logger;
    
    @Test
    public void shouldLogErrorDueToBigPacketSize() {
        packetFilter = ...;
        PcapPacket packet = ...;
        packetFilter.appendAndSend(packet);
        verify(logger).error(startsWith("{}: Received a packet with wire length"), any(), any(), any());
    }

但我找不到匹配 lambda 参数的方法。 any() 通配符不起作用。 Mockito 告诉我:

    Wanted but not invoked:
    logger.error(
        startsWith("{}: Received a packet with wire length"),
        <any>,
        <any>,
        <any>
    );
    -> at ...PacketFilterTest.shouldLogErrorDueToBigPacketSize(PacketFilterTest.java:143)
    
    However, there were exactly 1 interaction with this mock:
    logger.error(
        "{}: Received a packet with wire length {} larger than configured snapshot length {}",
        ...PacketFilterImpl$$Lambda2/0x0000000100348828@47829d6d,
        ...PacketFilterImpl$$Lambda3/0x0000000100348a40@2f677247,
        ...PacketFilterImpl$$Lambda4/0x0000000100348c60@43f03c23
    );

我可以使用什么来代替 any() 来匹配 lambda?

以下是显示问题的完整可运行示例 (gradle)

build.gradle:

plugins {
    id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.apache.logging.log4j:log4j-core:2.13.3'
    implementation 'junit:junit:4.13.1'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
    testImplementation 'org.mockito:mockito-core:3.3.3'
}

test {
    useJUnitPlatform()
}

Main.java

package org.example.lambdatest;

import org.apache.logging.log4j.Logger;

public class Main {
    private final Logger logger;

    public Main(Logger logger) {
        this.logger = logger;
    }

    public void log(String text1, String text2, String text3) {
        logger.error("This is my error text: {} {} {}", () -> text1, () -> text2, () -> text3);
    }
}

MainTest.java:

package org.example.lambdatest;

import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

public class MainTest {
    @Test public void testLogging() {
        Logger logger = mock(Logger.class);
        Main m  = new Main(logger);
        m.log("Something", "is", "wrong");
        verify(logger).error(startsWith("This is"), any(), any(), any());
    }
}

谢谢, 马丁

问题是将方法调用分派到 Logger class 中的重载 error(...) 方法。

public class MainTest {
    public static class Main {
        private final Logger logger;
        public Main(Logger logger) {
            this.logger = logger;
        }
        public void log(String text1, String text2) {
            // following call will reference error(String message, Supplier<?>... paramSuppliers) in Logger
            logger.error("This is my error text: {} {}", () -> text1, () -> text2); 
        }
    }
    @Test public void testLogging() {
        Logger logger = mock(Logger.class);
        Main m  = new Main(logger);
        m.log("Something", "is wrong");
        // following call will reference error(String message, Object p0, Object p1) in Logger
        verify(logger).error(eq("This is my error text: {} {}"), any(), any()); 
    }
}

由于它们将引用不同的方法,Mockito 会将其标记为不匹配。

该问题的一个解决方案是通过将 any() 个匹配器打包在一个数组中来帮助 java 分派到相同的重载 error(...) 方法:

public class MainTest {
    public static class Main {
        private final Logger logger;
        public Main(Logger logger) {
            this.logger = logger;
        }
        public void log(String text1, String text2) {
            logger.error("This is my error text: {} {}", () -> text1, () -> text2);
        }
    }
    @Test public void testLogging() {
        Logger logger = mock(Logger.class);
        Main m  = new Main(logger);
        m.log("Something", "is wrong");
        verify(logger).error(eq("This is my error text: {} {}"), new Supplier[]{any(), any()});
    }
}

现在对 error(...) 的两个方法调用将引用 Logger

中的相同方法