spock测试中的模拟java类没有被执行

时间:2018-03-15 14:44:35

标签: java unit-testing mocking spock

我正在尝试使用spock框架对某些java类进行单元测试。结构如下所示:

  • RequestProcessor(班级名称:com.myorg.query
  • DatabaseQuery(班级名称:public class RequestProcessor { private String request; public RequestProcessor(String aRequest) { this.request = request; } public String processRequest() { String response ; //do something here try { if(condition meets) { response = executeRequest(); } } catch ( various exceptions... ) { System.out.println("something went wrong..."); } } private String executeRequest() throws <<exceptions thrown by DatabaseQuery>> { //do something here DatabaseQuery queryResult = new DatabaseQuery(request) } }

第一堂课看起来像这样:

RequestProcessor

我正在尝试为DatabaseQuery类编写Spock测试,该测试依赖于DatabaseQuery。我正在考虑模仿RequestProcessor类,以便单独测试RequestProcessor类。

正在调用processRequest()的{​​{1}}方法,该方法依赖于另一种私有方法。该方法将使用DatabaseQuery来获取实际的查询结果。这就是我的Spock测试的样子:

class RequestProcessorSpec extends Specification {

    //Class to be tested
    RequestProcessor requestProcessor

    //Dependencies
    DatabaseQuery dbquery

    def "Given a valid request, dbquery's executeQuery method is called" () {
        given: "a valid request"
            def queryRequest = '{"info1":"value1","info2":"value2","query":"select * from users"}'

        and: "mock the DBQuery class"
            dbquery = Mock(DatabaseQuery)

        and: "create a new request"
            requestProcessor = new RequestProcessor(queryRequest)

        when: "the request is processed"
            requestHandler.processRequest()

        then: "dbquery executeQuery method is called"
            1 * dbquery.executeQuery(_ as String)
    }
 }

这不适合我。我收到了一个错误:

  • 调用次数太少
  • 无与伦比的调用

当我使用gradlew test --info运行测试以获得更多结果时,我看到控制台上打印的日志由processRequest方法中的try-catch语句捕获。

我在这里做错了什么?

1 个答案:

答案 0 :(得分:2)

示例代码中的问题

首先,您的示例代码不起作用,即使我简化它并创建我自己的虚拟DatabaseQuery类,因为此处至少有三个错误:

  • 在构造函数中,您有this.request = request(自我分配),但应该是this.request = aRequest;
  • 在测试中,您有requestHandler.processRequest(),但应该是requestProcessor.processRequest()
  • 方法executeRequest()未按规定返回String。所以我只能推测它实际上会在DatabaseQuery上调用另一种方法,以便将查询结果转换为String

为什么模拟不起作用?

完全避免这种情况后,让我们看看你的测试真正的根本错误。

  

我在这里做错了什么?

假设局部变量中的mock在应用程序代码中的其他局部变量中以某种方式有效。为了使用mock,您需要将它注入到被测试的类中。但是和许多开发人员一样,你没有通过依赖注入来设计解耦和可测试性,但是你在内部创建了你的依赖 - 在这种情况下是DatabaseQuery对象。

测试还有什么问题?

我认为您的测试只会受到过度规范的影响。为什么要检查另一个类中的特定方法是否从私有方法调用(间接地,在那个)?您不直接测试私有方法,而是通过调用公共方法来覆盖它们的代码,并且您的测试已经这样做了。

如果您想覆盖catch块,只需确保您的请求导致正确的例外情况。也许你需要为此模拟数据库连接并确保它将预期结果返回给DatabaseQuery。我没有看到足够的代码才能准确说出来。

原始问题的技术解决方案

现在让我们假设您绝对想要检查这种互动,无论我之前说过什么。您需要做什么取决于具体情况(您未在代码中显示):

在任何情况下,您都需要DatabaseQuery注射。您只需在您的班级中添加成员和其他设置者即可。

现在你的路上有一个分叉,取决于交互dbquery.executeQuery(_ as String)来自哪里(被叫):

  1. 如果从外部DatabaseQuery调用该方法,则可以注入正常的Mock
  2. 如果从DatabaseQuery内部调用该方法,则需要注入Spy,因为模拟不会像原始对象一样调用其他内部方法,因为 - 好吧,它只是一个模拟。 / LI>

    案例1:从外部调用executeQuery(String)

    package de.scrum_master.query;
    
    public class DatabaseQuery {
      private String request;
    
      public DatabaseQuery(String request) {
        this.request = request;
      }
    
      public String executeQuery(String request) {
        return request.toUpperCase();
      }
    
      public String getResult() {
        return executeQuery(request);
      }
    }
    
    package de.scrum_master.requests;
    
    import de.scrum_master.query.DatabaseQuery;
    
    public class RequestProcessor {
      private String request;
      private DatabaseQuery databaseQuery;
    
      public RequestProcessor(String aRequest) {
        this.request = aRequest;
        databaseQuery = new DatabaseQuery(request);
      }
    
      public String processRequest() {
        return executeRequest();
      }
    
      private String executeRequest() {
        return databaseQuery.executeQuery(request);
        //return databaseQuery.getResult();
      }
    
      public void setDatabaseQuery(DatabaseQuery databaseQuery) {
        this.databaseQuery = databaseQuery;
      }
    }
    
    package de.scrum_master.requests
    
    import de.scrum_master.query.DatabaseQuery
    import spock.lang.Specification
    
    class RequestProcessorTest extends Specification {
      //Class to be tested
      RequestProcessor requestProcessor
    
      //Dependencies
      DatabaseQuery dbquery
    
      def "Given a valid request, dbquery's executeQuery method is called" () {
        given: "a valid request"
        def queryRequest = '{"info1":"value1","info2":"value2","query":"select * from users"}'
    
        and: "mock the DBQuery class"
        dbquery = Mock(DatabaseQuery)
        //dbquery = Spy(DatabaseQuery, constructorArgs: [queryRequest])
    
        and: "create a new request"
        requestProcessor = new RequestProcessor(queryRequest)
        requestProcessor.databaseQuery = dbquery
    
        when: "the request is processed"
        requestProcessor.processRequest()
    
        then: "dbquery executeQuery method is called"
        1 * dbquery.executeQuery(_ as String)
      }
    }
    

    现在测试工作包括交互检查。

    案例2:executeQuery(String)从其自己的班级内部调用

    您是否在RequestProcessorRequestProcessorTest中看到了两条已注释掉的行?只需使用它们并注释掉其他两个,而不是这样:

      private String executeRequest() {
        //return databaseQuery.executeQuery(request);
        return databaseQuery.getResult();
      }
    
        and: "mock the DBQuery class"
        //dbquery = Mock(DatabaseQuery)
        dbquery = Spy(DatabaseQuery, constructorArgs: [queryRequest])
    

    测试仍然有效,包括互动检查。

    当然,我必须假装一些东西并填写你没有提供的缺失拼图瓷砖,但这基本上就是它的工作方式。