使用JMockit模拟测试类中的私有方法

14

我希望能够模拟被测试类中的私有方法,但当该方法被调用前两次时返回false,在此之后应该返回true。以下是我尝试的代码。这是正在进行测试的类:

public class ClassToTest 
{
    public void methodToTest()
    {
        Integer integerInstance = new Integer(0);
        boolean returnValue= methodToMock(integerInstance);
        if(returnValue)
        {
            System.out.println("methodToMock returned true");
        }
        else
        {
            System.out.println("methodToMock returned true");
        }
        System.out.println();
    }
    private boolean methodToMock(int value)
    {
        return true;
    }
}

测试类

import org.junit.Test;
import static mockit.Deencapsulation.*;

import mockit.*;
public class TestAClass 
{
    @Tested ClassToTest classToTestInstance;
    @Test
    public void test1()
    {

        new NonStrictExpectations(classToTestInstance)
        {
            {
                invoke(classToTestInstance, "methodToMock", anyInt);
                returns(false);
                times = 2;

                invoke(classToTestInstance, "methodToMock", anyInt);
                returns(true);
                times = 1;

            }
        };

        classToTestInstance.methodToTest();
        classToTestInstance.methodToTest();
        classToTestInstance.methodToTest();

    }
}

我这样做是为了达到预期的结果

    final StringBuffer count = new StringBuffer("0");
    new NonStrictExpectations(classToTestInstance)
    {

        {
            invoke(classToTestInstance, "methodToMock", anyInt);
            result= new Delegate() 
            {
                boolean methodToMock(int value)
                {                   
                    count.replace(0, count.length(), Integer.valueOf(Integer.valueOf(count.toString())+1).toString());
                    if(Integer.valueOf(count.toString())==3)
                    {
                        return true;
                    }
                    return false;
                }
            };

        }
    };

如果你想模拟一个私有方法,那听起来很像你应该将该方法的功能移动到另一个类中。 - Icewind
这是被测试类的私有方法。 - Varun
私有方法不应该被测试吗? - Icewind
我正在测试一个公共方法,并想模拟私有方法。 - Varun
我不认为这个问题应该被关闭,因为它所引用的“重复”与另一个问题有关——如何测试私有方法。这个问题明确表明需要模拟一个私有方法,但是测试一个公共方法,其中正在对被测试类中的私有方法进行模拟。 - Eric B.
1
很不幸,自1.46版本以来,在Deencapsulation中已经没有invoke()方法了,并且在1.47版本中删除了Deencapsulation类。很遗憾,MockUp也无法模拟私有方法。这真是可惜。 - kap
3个回答

15

使用Expectations(或StrictExpectations)

结合Expectations和Deencapsulation.invoke()的方法,您可以对被测试对象进行部分mock:

import org.junit.Test;
import static mockit.Deencapsulation.*;
import mockit.*;

public class TestAClass
{
    public static class ClassToTest 
    {
        public void methodToTest()
        {
            boolean returnValue = methodToMock(0);
            System.out.println("methodToMock returned " + returnValue);
        }

        private boolean methodToMock(int value) { return true; }
    }

    @Tested ClassToTest classToTestInstance;

    @Test
    public void partiallyMockTestedClass() {
        new Expectations(classToTestInstance) {{
            invoke(classToTestInstance, "methodToMock", anyInt);
            result = false;
            times = 2;
        }};

        classToTestInstance.methodToTest();
        classToTestInstance.methodToTest();
        classToTestInstance.methodToTest();
    }
}

上面的测试打印出:

methodToMock returned false
methodToMock returned false
methodToMock returned true

通常情况下,我们应该避免嘲笑private方法。尽管如此,在实践中,我发现有时这样做是有用的,特别是当您拥有一个执行非平凡操作且已由另一个测试进行测试的私有方法时;在这种情况下,对该私有方法进行模拟测试(无论是针对不同的公共方法还是通过同一公共方法的不同路径)可能比设置必要的输入/条件要容易得多。

使用NonStrictExpectations(在JMockit 1.23中已弃用)

使用NonStrictExpectations编写测试同样容易(原始尝试失败仅因为相同的非严格期望被记录了两次,第二次记录覆盖了第一次):

@Test
public void partiallyMockTestedClass() {
    new NonStrictExpectations(classToTestInstance) {{
        invoke(classToTestInstance, "methodToMock", anyInt);
        returns(false, false, true);
    }};

    classToTestInstance.methodToTest();
    classToTestInstance.methodToTest();
    classToTestInstance.methodToTest();
}

使用委托

如果需要更多的灵活性,我们可以记录一个基于Delegate的结果:

@Test
public void partiallyMockTestedClass() {
    new NonStrictExpectations(classToTestInstance) {{
        invoke(classToTestInstance, "methodToMock", anyInt);

        result = new Delegate() {
           boolean delegate() {
               boolean nextValue = ...choose next return value somehow...
               return nextValue;
           }
        }
    }};

    classToTestInstance.methodToTest();
    classToTestInstance.methodToTest();
    classToTestInstance.methodToTest();
}

感谢您的支持,根据您在我另一个问题中的回答,用新的Expectations(classToTestInstance)在实际方法运行后模拟该方法两次,我想在这里返回"true"而不是实际值。在methodToMock的定义中,它只是简单地返回"true",我想在这里问一下,如果在methodToMock的定义中有逻辑,然后mehtodToMock返回一些值并且我们想从中返回"true",那么我们应该如何进行mock? - Varun
不确定这是否符合您的需求,但您可以始终记录一个委托对象(result = new Delegate() { ... };),以便根据某些任意逻辑返回值。 - Rogério
谢谢@Rogério。请将此评论作为答案提供,我会接受这个答案。 - Varun
所描述的方法不是用于部分模拟,而是仅用于完全模拟。我们不能使用已经存在的类/实例方法和字段。有时它可能会有用,但非常罕见。Kaushik的答案更加有用。 - Gangnus
在JMockit 1.27中,NonStrictExpectations被移除了;在JMockit 1.23中,使用Expectations模拟私有方法的能力也被移除了。因此,现在您必须使用MockUp,请参见@Kaushik的答案。 - dag

13

这对我有效:

        new MockUp<ClassToTest>() {
            @Mock
            boolean methodToMock(int value) {
                return true;
            }
        };

2
@Gangnus 私有方法模拟(使用 @Mocked)自 JMockit 1.23 起已被停用,并且在版本 1.27 中使用 MockUp 时将受到限制。我的答案是错误的,因为它仍然假定在某些情况下模拟私有方法可能是有用的;但实际上并不是这样。 - Rogério
2
有很多人想找到如何模拟私有方法的方法,其中12人甚至在这里投票支持了一些答案。至于我,我搜索了一个解决方案,因为我需要它。而且我找到了它。你怎么能说没有人需要它呢?你回答的问题不同 - 它太复杂了,当明显人们需要简单的东西时。它声称提供完整模拟,而实际上只提供了部分模拟。我相信你是该主题的非常优秀的专家。但对我来说,你的解释太复杂了。 - Gangnus
我正在使用jmockit 1.18,这对我有效,而@Rogério的答案则无效。 - simomo
2
JMockit似乎在模拟私有方法方面有所改变。无论我们个人对此持何种意见,也许这应该在主要版本发布时完成?关于测试私有方法的话题,我的观点是,有很多(遗留)代码需要我们编写有效的表征测试——模拟私有方法是非常有用的功能。我认为JMockit是处理这些情况的首选工具——任何东西都可以被模拟,而不受当前编程最佳实践的影响。 - beluchin
1
@EricB。它仍然存在,而且不会消失。 - Rogério
显示剩余3条评论

2
在这里,您可以使用模拟行为覆盖测试类的特定方法。
以下是代码示例:
public class ClassToTest 
{
    public void methodToTest()
    {
        Integer integerInstance = new Integer(0);
        boolean returnValue= methodToMock(integerInstance);
        if(returnValue)
        {
            System.out.println("methodToMock returned true");
        }
        else
        {
            System.out.println("methodToMock returned true");
        }
        System.out.println();
    }
    private boolean methodToMock(int value)
    {
        return true;
    }
}

测试类应该是:

public class ClassToTestTest{

    @Test
    public void testMethodToTest(){

        new Mockup<ClassToTest>(){
            @Mock
            private boolean methodToMock(int value){
                return true;
            }
        };

        ....    

    }
}

1
你使用的是哪个版本? - spandey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接