【问题标题】:Unit testing Symfony application using FOSElasticaBundle without an ES Server?在没有 ES 服务器的情况下使用 FOSElasticaBundle 对 Symfony 应用程序进行单元测试?
【发布时间】:2014-07-26 02:55:23
【问题描述】:

我有一个包含一组现有单元测试的应用程序,这些单元测试使用 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);

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

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

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

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

【问题讨论】:

    标签: symfony elasticsearch elastica foselasticabundle


    【解决方案1】:

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

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

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

    客户端代码

    <?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
            ]
          }
        ]
      }
    }
    

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

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

    【讨论】:

    • 这对我不起作用..我正在使用 fos elastica finder 类,可能是相关的吗?
    • 我不确定。这是一个旧项目,我很长一段时间没有对其进行任何更新。但是,@seltzlab 在下面提到的解决方案 - 禁用侦听器,以及像我在初始问题代码 sn-p 中所做的那样模拟分页适配器可能对您有用。
    【解决方案2】:

    您可以尝试在 config_test.yml(或您的测试环境名称)中禁用事件侦听器。

    fos_elastica:
        indexes:
            your_index_name:
                types:
                    your_type_name:
                        persistence:
                            listener:
                                insert: false
                                update: false
                                delete: false
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-30
      • 2021-10-06
      • 2016-03-11
      • 1970-01-01
      • 2012-03-26
      • 2017-03-13
      • 2014-07-29
      • 1970-01-01
      相关资源
      最近更新 更多