symfony - 在没有 ES 服务器的情况下使用 FOSElasticaBundle 对 Symfony 应用程序进行单元测试?

标签 symfony elasticsearch elastica foselasticabundle

我有一个包含一组现有单元测试的应用程序,这些单元测试使用 SQLite 作为数据库。我最近通过 ES 添加了搜索功能,它取代了许多用于直接查询数据库的端点操作。我想在不测试 ES 本身的情况下测试与这些端点相关的所有业务逻辑,这意味着没有可用的 ES 服务器。我计划在一组集成测试中测试 ES 本身,以降低运行频率。

我的问题是试图准确追踪执行流程的情况。

我的第一个倾向是简单地创建 FOSElasticaBundle 为我的索引创建的 ES Finder 的模拟对象。因为我使用的是分页,所以结果比我想象的要复杂:

    // code context: test method in unit test extending Symfony's WebTestCase
    $client = $this->getClient();


    $expectedHitCount = 10;

    // Setup real objects which (as far as I can tell) don't act upon the ES client
    // and instead only hold / manipulate the data.
    $responseString = file_get_contents(static::SEARCH_RESULT_FILE_RESOURCE);
    $query = SearchRepository::getProximitySearchQuery($lat, $lng, $radius, $offset, $limit);
    $response = new Response($responseString, 200);        
    $resultSet = new RawPartialResults(new ResultSet($response, $query ));

    // Create a mock pagination adapter which is what my service expects to be returned from
    // the search repository.
    $adapter = $this->getMockBuilder('FOS\ElasticaBundle\Paginator\RawPaginatorAdapter')
                    ->disableOriginalConstructor()
                    ->getMock();
    $adapter->method('getTotalHits')->will($this->returnValue($expectedTotalCount));
    $adapter->method('getResults')->will($this->returnValue($resultSet));
    $adapter->method('getQuery')->will($this->returnValue($query));

    $es = $this->getMockBuilder(get_class($client->getContainer()->get(static::ES_FINDER_SERVICE)))
               ->disableOriginalConstructor()
               ->getMock();
    $es->method('createPaginatorAdapter')->will($this->returnValue($adapter));

    // Replace the client container's service definition with our mock object
    $client->getContainer()->set(static::ES_FINDER_SERVICE, $es);

这实际上一直有效,直到我从 Controller 返回 View 。我的服务从我存储在文件中的 JSON 搜索响应中获取带有预填充结果集的模拟分页适配器(随后传递到我的 ResultSet 对象中)。但是,一旦我返回 View ,似乎有一个监听器尝试使用 Query 再次查询 ES,而不是使用我已经传入的 ResultSet。

我似乎找不到这个听众。我也不明白为什么当 ResuletSet 已经存在时它会尝试查询。

我也在使用 FOSRestBundle,并利用他们的 ViewListener 来自动序列化我返回的任何内容。我也没有在该流程中看到任何嫌疑人。我认为这可能与结果集的序列化有关,但到目前为止还无法追踪到有问题的代码。

之前有没有人尝试过类似的事情,并且对如何调试我当前的设置或替代的更好的设置来模拟 ES 进行此类测试有任何建议?

最佳答案

在四处挖掘之后,我找到了一个不涉及使用模拟对象的替代解决方案。如果有人有更好的方法,我将暂时保持开放状态,但我决定同时采用的方法是在我的测试环境中覆盖客户端。

FOSElasticaBundle 在这里有一个覆盖客户端的示例:https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/cookbook/suppress-server-errors.md

我能够以这样一种方式覆盖客户端,即我可以从请求中创建一个唯一的 key ,然后根据该 key 提供响应,本质上是为所有已知请求 stub 服务器。对于不匹配的请求,我返回默认的空响应。这对我来说足够好。

客户代码

<?php

namespace Acme\DemoBundle\Tests\Elastica;

use Elastica\Request;
use Elastica\Response;
use FOS\ElasticaBundle\Client as BaseClient;

class Client extends BaseClient
{
    /**
     * This array translates a key which is the md5 hash of the Request::toString() into
     * a human friendly name so that we can load the proper response from a file in the
     * file system.
     * 
     * @var array
     */
    protected $responseLookup = array(
        '7fea3dda860a424aa974b44f508b6678' => 'proximity-search-response.json'
    );

    /**
     * {@inheritdoc}
     */
    public function request($path, $method = Request::GET, $data = array(), array $query = array())
    {
        $request = new Request($path, $method, $data, $query);
        $requestKey = md5($request->toString());
        $this->_log($request);
        $this->_log("Test request lookup key: $requestKey");

        if (!isset($this->responseLookup[$requestKey])
            || !$response = file_get_contents(__DIR__ . "/../DataFixtures/Resources/search/{$this->responseLookup[$requestKey]}")) {
            return $this->getNullResponse();
        }

        return new Response($response);
    }

    public function getNullResponse()
    {
        $this->_log("Returning NULL response");
        return new Response('{"took":0,"timed_out":false,"hits":{"total":0,"max_score":0,"hits":[]}}');
    }
}

配置变更
// file: config_test.yml
parameters:
    fos_elastica.client.class: Acme\DemoBundle\Tests\Elastica\Client

示例响应文件 (proximity-search-response.json)
{
  "took": 7,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": null,
    "hits": [
      {
        "_index": "search",
        "_type": "place",
        "_id": "1",
        "_score": null,
        "_source": {
          "location": "40.849100,-73.644800",
          "id": 1,
          "name": "My Place"
        },
        "sort": [
          322.52855474383045
        ]
      }
    ]
  }
}

该解决方案运行良好且速度快,但维护起来很痛苦。如果请求发生任何变化,您需要从日志中检索新的请求 key ,在数组中更新它,并使用新请求的新响应数据更新文件。我通常只是直接 curl 服务器并从那里修改它。

我很想看到任何其他可能更简单的解决方案,但我希望这同时对其他人有所帮助!

关于symfony - 在没有 ES 服务器的情况下使用 FOSElasticaBundle 对 Symfony 应用程序进行单元测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24967063/

相关文章:

elastica - 如何保护 Elasticsearch

Symfony 2 和 twig : Add a class for 'current page' in navigation

php - 交响乐 2 : How to avoid the sessions table being dropped by doctrine migrations?

elasticsearch - logstash - 过滤日志并发送到不同的 Elasticsearch 集群

apache-spark - 为什么在提交到 YARN 集群时,elasticsearch-spark 5.5.0 会因 AbstractMethodError 而失败?

php - 在 elasticaserch 中应用游标分页

symfony - security.yml 中的未知实体命名空间与 Symfony 2 中的自定义实体文件夹

symfony - 检索服务中的参数 - Symfony

elasticsearch - 在 Elasticsearch 中过滤每个组的最新文档

php - 如何在elastica php中一次性更新多个数据?