代码之家  ›  专栏  ›  技术社区  ›  pszent

Java Mockito停留在单例方法的doReturn上

  •  1
  • pszent  · 技术社区  · 8 年前

    我有一个单例类来帮助我从控制台读取输入:

    public class IOHelper {
        public org.slf4j.Logger logger = Logger.logger;
    
        //JLine
        public ConsoleReader cr;
    
        private static IOHelper instance;
    
        private IOHelper(){
            {
                try {
                    cr = new ConsoleReader();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static synchronized IOHelper getInstance(){
            if (instance == null){
                instance = new IOHelper();
            }
    
            return instance;
        }
    

    我要测试的代码将其称为:

    String in = IOHelper.getInstance().cr.readLine();
    

    然后我的测试课:

    class Test {
    
        private static NetworkCommunicator networkCommunicator;
        private static IOHelper ioHelper;
    
        @BeforeAll
        static void setUpClass() throws Throwable {
    
            ioHelper = spy(IOHelper.getInstance());
            doReturn("1").when(ioHelper).cr.readLine();
    
            networkCommunicator = spy(NetworkCommunicator.class);
    
            doNothing().when(networkCommunicator).connectToServer();
            doNothing().when(networkCommunicator).connectToOtherServer();
        }
    

    doReturn("1").when(ioHelper).cr.readLine(); 行,好像它实际执行了 cr.readline(); 部分我的stacktrace指向该方法 private native int read0() throws IOException; 在FileInputStream上找到。评论表明,如果没有可用的输入,它将被阻止。 我想替换方法 readLine() 在我的控制台上,因此当我的CLI请求输入时,我的测试可以“伪造”该输入。

    编辑:两个有趣线程的调用堆栈:

    "main@1" prio=5 tid=0x1 nid=NA runnable
      java.lang.Thread.State: RUNNABLE
         blocks NonBlockingInputStreamThread@1437
          at java.io.FileInputStream.read0(FileInputStream.java:-1)
          at java.io.FileInputStream.read(FileInputStream.java:207)
          at jline.internal.NonBlockingInputStream.read(NonBlockingInputStream.java:166)
          - locked <0x67d> (a jline.internal.NonBlockingInputStream)
          at jline.internal.NonBlockingInputStream.read(NonBlockingInputStream.java:135)
          at jline.internal.NonBlockingInputStream.read(NonBlockingInputStream.java:243)
          at jline.internal.InputStreamReader.read(InputStreamReader.java:257)
          at jline.internal.InputStreamReader.read(InputStreamReader.java:194)
          at jline.console.ConsoleReader.readCharacter(ConsoleReader.java:2147)
          at jline.console.ConsoleReader.readCharacter(ConsoleReader.java:2137)
          at jline.console.ConsoleReader.readBinding(ConsoleReader.java:2222)
          at jline.console.ConsoleReader.readLine(ConsoleReader.java:2463)
          at jline.console.ConsoleReader.readLine(ConsoleReader.java:2374)
          at jline.console.ConsoleReader.readLine(ConsoleReader.java:2362)
          at jline.console.ConsoleReader.readLine(ConsoleReader.java:2350)
          at com.mypkg.Test.setUpClass(Test.java:43)
          at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
          at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:389)
          at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
          at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.lambda$invokeBeforeAllMethods$5(ClassTestDescriptor.java:228)
          at org.junit.jupiter.engine.descriptor.ClassTestDescriptor$$Lambda$162.715378067.execute(Unknown Source:-1)
          at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
          at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.invokeBeforeAllMethods(ClassTestDescriptor.java:227)
          at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.before(ClassTestDescriptor.java:151)
          at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.before(ClassTestDescriptor.java:61)
          at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$execute$3(HierarchicalTestExecutor.java:80)
          at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$$Lambda$134.398690014.execute(Unknown Source:-1)
          at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
          at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:77)
          at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$null$2(HierarchicalTestExecutor.java:92)
          at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$$Lambda$137.1353170030.accept(Unknown Source:-1)
          at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
          at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
          at java.util.Iterator.forEachRemaining(Iterator.java:116)
          at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
          at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
          at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
          at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
          at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
          at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
          at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
          at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$execute$3(HierarchicalTestExecutor.java:92)
          at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$$Lambda$134.398690014.execute(Unknown Source:-1)
          at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
          at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:77)
          at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:51)
          at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)
          at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
          at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
          at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
          at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:62)
          at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
          at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
          at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    
    "NonBlockingInputStreamThread@1437" daemon prio=5 tid=0xf nid=NA waiting
      java.lang.Thread.State: WAITING
         waiting for main@1 to release lock on <0x67d> (a jline.internal.NonBlockingInputStream)
          at java.lang.Object.wait(Object.java:-1)
          at jline.internal.NonBlockingInputStream.run(NonBlockingInputStream.java:275)
          at java.lang.Thread.run(Thread.java:745)
    

    扩展这个问题:我有一些方法要求用户多个输入(例如更新一些设置)。我认为最好的方法是将设置重构为一个只接受参数并测试这个新方法的方法,对吗?有没有一种解决方案,当任何方法试图从 ConsoleReader ? 我想过使用 Robot

    1 回复  |  直到 8 年前
        1
  •  0
  •   BeUndead    8 年前

    看起来你在嘲笑错误的事情。你想和 ConsoleReader 的返回。所以有两种选择:


    移动以使用一些 getConsoleReader() 方法 IOHelper 类,然后您可以模拟它-您需要确保 IOHelper

    private final IOHelper mySpy = spy(IOHelper.getInstance());
    
    @Before
    public void setup() {
        final ConsoleReader mockCR = mock(ConsoleReader.class);
        // Any mockery on your mockCR you need.
        // doReturn(...).when(mockCR).readLine();, etc.
        doReturn(mockCR).when(mySpy).getConsoleReader();
    }
    

    将cr字段修改为模拟。例如。

    private final IOHelper ioHelper= IOHelper.getInstance();
    
    @Before
    public void setup() {
        final ConsoleReader mockCR = mock(ConsoleReader.class);
        // Any mockery on your mockCR you need.
        // doReturn(...).when(mockCR).readLine();, etc.
        ioHelper.cr = mockCR;
    }
    

    控制台阅读器 是 public final ),这就是一个要求。即使字段是私有的,也可以使用一些助手库来处理它。spring和apache-commons-lang3都提供了这种类型的实用程序。


    使用Powermock修改 控制台阅读器

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(IOHelper.class)
    public class IOHelperTest {
        @BeforeClass
        public static void setup() {
            final ConsoleReader mockCR = mock(ConsoleReader.class);
            // Any mockery on your mockCR you need.
            // doReturn(...).when(mockCR).readLine();, etc.
    
            PowerMock.whenNew(ConsoleReader.class).thenReturn(mockCR);
        }
    }
    

    最后,您可以修改 IOHelper 上课 控制台阅读器 作为构造函数参数,只需提供 mockCR 从上述所有方法中(并使其不 private ).