【发布时间】:2018-11-13 00:13:56
【问题描述】:
没有问题的上下文
假设在 Symfony 3.3 中,我们有一个默认的控制器来绘制“Hello, world!”:
class DefaultController extends Controller
{
public function indexAction() : Response
{
return new Response( 'Hello, world!' );
}
}
如果我想测试它,我只是创建一个 WebTestCase 并在客户端或爬虫上做一些断言,例如
class DefaultControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request( 'GET', '/route/to/hello-world/' );
$this->assertEquals( 200, $client->getResponse()->getStatusCode() );
$this->assertContains( 'Hello', $crawler->filter( 'body' )->text() );
}
}
这很好用。
有问题的上下文
假设我们有一些经过单元测试的服务。例如IdGenerator 服务,它在我们需要时根据某种算法创建新的 Id,它们只是纯文本:
class IdGenerator
{
public function generateNewId() : string;
}
假设我们通过 autowiring 将其注入控制器。我们希望控制器会说出类似Hello, world, on request 8bfcedbe1bf3aa44e0545375f0e52f6b969c50fb! 的内容,其中这串字符来自 IdGenerator。
class DefaultController extends Controller
{
public function indexAction( IdGenerator $idGenerator ) : Response
{
$id = $idGenerator->generateNewId();
$message = sprintf( 'Hello, world, on request %s!', $id );
return new Response( $message );
}
}
当然,我可以在浏览器中多次重新加载页面,每次都会看到文本随着时间的推移使用新的 ID 发生变化。
但这不是自动化测试应有的方式。我们应该模拟 IdGenerator,所以它返回一个特定的id 来断言:
class DefaultControllerTest extends WebTestCase
{
public function testIndex()
{
$id = 'test-id-test-id-test-id';
$idGenerator = $this->createMock( IdGenerator::class );
$idGenerator->method( 'generateNewId' )->willReturn( $id );
// What do I have to do with $idGenerator now here????
$client = static::createClient();
// Do something else here?
$crawler = $client->request( 'GET', '/admin/flights/search/' );
$this->assertEquals( 200, $client->getResponse()->getStatusCode() );
$this->assertContains( $id, $crawler->filter( 'body' )->text() );
}
}
我已经测试过从内核、客户端获取容器,并在那里设置服务,它确实不工作:
$id = 'test-id-test-id-test-id';
$idGenerator = $this->createMock( IdGenerator::class );
$idGenerator->method( 'generateNewId' )->willReturn( $id );
$client = static::createClient();
$kernel = $client->getKernel();
$container = $kernel->getContainer();
$container->set( 'My\Nice\Project\Namespace\IdGenerator', $idGenerator );
它仍然得到自动装配的,而不是我想要的(模拟)。
问题
如何设置 WebTestCase 以便自动装配连接我的模拟服务?
【问题讨论】:
-
我已经多次看到这类问题了。 “自动化”测试从单元测试到测试最终软件的功能/集成测试,所有依赖项通常都是模拟的。您正试图通过尝试修改容器来做一些中间的事情。只是不会按照你想要的方式发生。容器已编译,您无法用新服务替换现有服务。您可以做的是严格执行 IdGenerator 服务以进行测试。但这违背了测试的目的。
-
那么也许是设计测试的问题?我的意思是,也许我的“单元测试”可能依赖于模拟,但我的“功能测试”不应该依赖于它们?
-
是的。这几乎涵盖了它。此外,依赖解析 html 响应会很快变得陈旧和脆弱。
标签: symfony mocking phpunit autowired functional-testing