如何模拟从类级别对象调用的方法

时间:2019-10-10 10:26:42

标签: java unit-testing mockito powermockito

我正在为A类编写单元测试,我想模拟一个方法,但是该方法是从类级别对象调用的,这将是我如何模拟这个方法。

让我从示例中进行解释

正在测试的A类。

public class ClassA {
    ClassB objectOfB = new ClassB();
    public int add(int a, int b) {
        int addition = objectOfB.performCalculation(a,b);
        return addition;
    }
}

B类,具有一些业务逻辑。

  public class ClassB {
    public int performCalculation(int a, int b) {
        int c = a+b;
        System.out.println("I am not mocked, I am actual call");
        System.out.println("Returning " + c + " From ClassB");
        return c;
    }
  }

书面测试

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassA.class, ClassB.class})
public class ClassATest {
    @InjectMocks
    ClassA objA = new ClassA();

    @Test
    public void testAddFromClassA() throws Exception {
        ClassB objB = Mockito.mock(ClassB.class);
        Mockito.when(objB.performCalculation(5, 10)).thenReturn(15);
        int result = objA.add(5, 10);
        assertEquals(result, 15);
    }

}

测试结果:

此测试通过了,但是它不是在模拟ClassB的方法,而是在执行实际的调用。


要求:

在编写测试时,我想模拟以下行:objectOfB.performCalculation(a,b);来自类A,但是如您所见,classB()的对象是在类级别创建的。

我该如何嘲笑呢?

我应该在测试课中写些什么。

enter image description here

2 个答案:

答案 0 :(得分:1)

模拟类的初始化,以便在执行测试时使用模拟

@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassA.class}) //prepare the class creating the new instance of ClassB for test, not the ClassB itself.
public class ClassATest {
    @Test
    public void testAddFromClassA() throws Exception {
        int expected = 15;
        ClassB objB = Mockito.mock(ClassB.class);
        Mockito.when(objB.performCalculation(5, 10)).thenReturn(expected);

        //mocking initialization of ClassB class withing ClassA class
        PowerMockito.whenNew(ClassB.class).withNoArguments().thenReturn(objB);

        ClassA objA = new ClassA();

        //Act
        int actual = objA.add(5, 10);

        //Assert
        assertEquals(expected, actual);
    }
}

参考How to mock construction of new objects

现在,理想情况下,目标类应通过构造函数注入遵循显式依赖原理

public class ClassA {
    final ClassB objectOfB;

    public ClassA(ClassB objectOfB) {
        this.objectOfB = objectOfB;
    }

    public int add(int a, int b) {
        int addition = objectOfB.performCalculation(a,b);
        return addition;
    }
}

允许类明确声明其执行设计功能所依赖的内容。

它还允许反转控制和松散耦合,这使得该类在维护和测试方面更加灵活

@RunWith(PowerMockRunner.class)
public class ClassATest {
    @Test
    public void testAddFromClassA() throws Exception {
        int expected = 15;
        ClassB objB = Mockito.mock(ClassB.class);
        Mockito.when(objB.performCalculation(5, 10)).thenReturn(expected);

        ClassA objA = new ClassA(objB);

        //Act
        int actual = objA.add(5, 10);

        //Assert
        assertEquals(expected, actual);
    }
}

仅仅因为PowerMockito允许模拟新对象的构造并不意味着我们应该这样做。

如果遵循正确的设计原则,那么确实没有必要进行此类破解。

答案 1 :(得分:0)

我建议您多读一些关于模拟以及模拟的方法。 查看您尝试运行的测试代码:

  ClassB objB = Mockito.mock(ClassB.class);
  Mockito.when(objB.performCalculation(5, 10)).thenReturn(15);
  int result = objA.add(5, 10);
  assertEquals(result, 15);

正在发生的事情
您正在模拟ClassB,但没有使用模拟的实例objB
当模拟一个类时,您会返回一个模拟的 instance ,这并不意味着该类现在已通过您的测试被模拟,这仅意味着您可以使用Mockito来操纵该特定的实例。 / p>

应该怎么办
如果您不能使用该实例来测试某个方法是否依赖于该方法(例如您的情况),则意味着这两个类之间具有非常强的依赖关系,并且如果这是测试的问题,通常意味着您的设计是有毛病。您应该可以通过在构造函数中为其提供参数来将ClassA注入到ClassB中。

public ClassA(ClassB bInstance){
   this.bIntance = bInstance
}

或(如果由于特定操作而自此)作为函数参数。

public add(ClassB classBInstance, int a, int b){
    classBInstance. performCalculation(a.b)
}

这将允许您模拟实例并使用您模拟的特定实现来运行ClassA函数:

ClassB classBInstance1 = Mockito.mock(ClassB.class)
ClassB classBInstance2 = Mockito.mock(ClassB.class)
Mockito.when(classBInstance1.performCalculation(5, 10)).thenReturn(15);
Mockito.when(classBInstance2.performCalculation(5, 10)).thenReturn(42); 
new ClassA(classBInstance1).add(5,10) //returns 15
new ClassA(classBInstance2).add(5,10) //returns 42
new ClassA().add(classBInstance1,5,10) //15
new ClassA().add(classBInstance2,5,10) //42