未执行 spock 测试中的模拟 java class

Mocked java class in spock test is not being executed

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

第一个 class 看起来像这样:

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 class 编写 Spock 测试,它依赖于 DatabaseQuery。我正在考虑模拟 DatabaseQuery class 以便简单地单独测试 RequestProcessor class。

正在调用RequestProcessorprocessRequest()方法,该方法依赖于另一个私有方法。该方法将使用 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 语句捕获。

我做错了什么?

示例代码中的问题

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

  • 在构造函数中你有 this.request = request (self-assignment),但它应该是 this.request = aRequest;.
  • 在测试中你有 requestHandler.processRequest(),但应该是 requestProcessor.processRequest()
  • 方法 executeRequest() 不 return 一个 String 指定。所以我只能推测实际上它在 DatabaseQuery 上调用了另一个方法,以便将查询结果转换为 String.

为什么模拟不起作用?

解决了这个问题后,让我们看看您的测试真正根本性的错误是什么。

What am I doing wrong here?

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

测试还有什么问题?

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

如果您想覆盖 catch 块,只需确保您的请求导致正确的异常。也许您需要为此模拟数据库连接并确保它 return 是 DatabaseQuery 的预期结果。我没有看到足够多的代码来准确地说。

原问题的技术解决方案

现在假设您绝对想检查此交互,无论我之前说过什么。你需要做什么取决于情况(你没有在你的代码中显示):

在任何情况下,您都需要使 DatabaseQuery 可注射。您可以只添加一个成员和另一个 setter 到您的 class.

现在你有一个岔路口,这取决于互动 dbquery.executeQuery(_ as String) 是从哪里进行(调用)的:

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

情况一: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) 是从其自身内部调用的 class

您看到 RequestProcessorRequestProcessorTest 中的两条 commented-out 行了吗?只需使用它们并注释掉其他两个,而不是像这样:

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

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

当然我不得不伪造一些东西并填写你没有提供的缺失的拼图块,但这基本上就是它的工作原理。