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