使用Mockito模拟内部方法

3

我是一名Mockito新手。我正在尝试通过模拟数据库交互来为服务编写JUnit测试:

我有以下类(仅代表实际类)

public class TestService{

    public Response getTestList(String type){
        list = getListbyType(type);

        return response.entity(list);
    }

    private List getListbyType(String type){
          ...
          ..
          TestDAO testdao = new Testdao(sqlconn);
          list = testdao.getListfromDB(type)
          return list;
   }
}

我的测试类大概是这样的:

public class TestServiceTest{

    @InjectMocks
    private TestService testService = new TestService();
    @Test
    public void getTestListTest(){

        List testlist = new List();
        tetslist.add("test1");

        TestDAO testdaomock = mock(TestDAO);
        when(testdaomock.getListfromDB("test")).thenreturn(list);

        list = testService.getTestList(test);
    }
}

但是当我运行这个测试时,它仍然会调用实际的数据库查询,并从数据库中检索值,而不是模拟的值,我应该模拟SQL连接和非默认构造函数吗?我一点头绪都没有。

-- 更新

正如其他人建议的那样,我将DAO实例化移到了我的服务构造函数中,并且使用了Spy,但是仍然调用了实际的DB查询,而不是模拟查询。


testdaomock.getListfromDB("test") 不应该编译通过,因为 private List getListbyType(String type) 是私有的。它是如何编译通过的呢? - user3458
getListbyType(String type) 是私有的,但 testdaomock.getListfromDB("test") 在 DAO 类中是公共的。 - user1933888
@RunWith(MockitoJUnitRunner.class) - WildDev
可能是Test class with a new() call in it with Mockito的重复。 - user3458
这个测试的问题实际上是被测试代码的设计不良:在“getter”调用中,不应该实例化任何DAO。 DAO实例的唯一位置应该是一个字段,在构造时实例化或在需要时延迟实例化(测试将执行前者)。 - Oleg Sklyar
2个回答

2
您的问题与以下陈述有关:
TestDAO testdao = new Testdao(sqlconn);

您从mock()中获取的TestDAO实例并不是在testdao.getListfromDB(type)中使用的实例,该方法后面有一个new关键字。
为了成功进行模拟,您需要贯彻依赖反转原则。这意味着new关键字只能出现在您不打算测试的类中,例如简单工厂或Spring配置文件。
【更新】
如果适当的IOC不是一个选项,您可以引入一个分配DAO的方法,然后对其进行spy()操作。该方法必须是包私有的。详细信息请参见此处
另一种选择是添加一个以testdao作为参数的包私有构造函数,并用该构造函数来表达您的默认构造函数。

这个意思很清楚,但我现在无法引入IOC,有没有一种方法可以模拟这个带参数的构造函数而不是默认的构造函数? - user1933888
你可以使用PowerMock,但是你需要使用一个单独的JUnit运行器,请参见https://github.com/jayway/powermock/wiki/MockitoUsage#how-to-mock-construction-of-new-objects。 - sandrozbinden
1
你可以尝试通过在 getTestList 方法中传递一个 TestDAO 实例的引用来将 TestDAO 注入到 TestService 中。另一种方法是在 TestService 中添加一个 TestDAO 属性,在默认构造函数中设置默认值,然后在 TestService 中添加一个 setter 来设置 TestDAO 属性。然后你可以在测试中使用这个 setter 来传递一个 TestDAO mock。 - Poger

2
如果您无法更改TestService类(旧代码),则可以使用PowerMockito(https://github.com/jayway/powermock)模拟新实例。这使用了自己的JUnit运行器(PowerMockRunner),允许字节码操作。
以下是您代码的示例:
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
public class TestServiceTest {

   @Mock
   TestDAO testDaoMock;

   @Test
   public void test() throws Exception {
    List<String> testlist = new ArrayList<>();
    testlist.add("test1");
    when(testDaoMock.getListfromDB(anyString())).thenReturn(testlist);
    PowerMockito.whenNew(TestDAO.class).withAnyArguments().thenReturn(testDaoMock);
    TestService testService = new TestService();
    Response actualResponse = testService.getTestList("testType");
    assertEquals(expectedResponse, actualResponse);
    }
}

您需要将 powermock-api-mockito 和 powermock-module-junit4 添加到您的依赖项中。

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