Unit test для издевательства метода, вызванного новым объектом класса
Я пишу unit test для существующего кода, который похож на этот
class someClass {
public function __construct() { ... }
public function someFoo($var) {
...
$var = "something";
...
$model = new someClass();
model->someOtherFoo($var);
}
public someOtherFoo($var){
// some code which has to be mocked
}
}
Здесь, как я должен быть в состоянии издеваться над вызовом функции "someOtherFoo
", чтобы он не выполнял "some code
" внутри someOtherFoo
?
class someClassTest {
public function someFoo() {
$fixture = $this->getMock('someClass ', array('someOtherFoo'));
$var = "something";
....
// How to mock the call to someOtherFoo() here
}
}
Можно ли издеваться над конструктором так, чтобы он возвращал мою собственную сконструированную функцию или переменную?
Спасибо
Ответы
Ответ 1
Где бы вы ни находились new XXX(...)
в тестируемом методе, вы обречены. Извлеките экземпляр нового метода - createSomeClass(...)
- того же класса. Это позволяет вам создать частичный макет тестируемого класса, который возвращает значение stubbed или mock из нового метода.
class someClass {
public function someFoo($var) {
$model = $this->createSomeClass(); // call method instead of using new
model->someOtherFoo($var);
}
public function createSomeClass() { // now you can mock this method in the test
return new someClass();
}
public function someOtherFoo($var){
// some code which has to be mocked
}
}
В тесте mock createSomeClass()
в экземпляре, на который вы вызываете someFoo()
, и mock someOtherFoo()
в экземпляре, который вы возвращаете из первого издевающегося вызова.
function testSomeFoo() {
// mock someOtherFoo() to ensure it gets the correct value for $arg
$created = $this->getMock('someClass', array('someOtherFoo'));
$created->expects($this->once())
->method('someOtherFoo')
->with('foo');
// mock createSomeClass() to return the mock above
$creator = $this->getMock('someClass', array('createSomeClass'));
$creator->expects($this->once())
->method('createSomeClass')
->will($this->returnValue($created));
// call someFoo() with the correct $arg
$creator->someFoo('foo');
}
Имейте в виду, что, поскольку экземпляр создает другой экземпляр того же класса, обычно задействуются два экземпляра. Вы можете использовать один и тот же экземпляр mock здесь, если он станет более понятным.
function testSomeFoo() {
$fixture = $this->getMock('someClass', array('createSomeClass', 'someOtherFoo'));
// mock createSomeClass() to return the mock
$fixture->expects($this->once())
->method('createSomeClass')
->will($this->returnValue($fixture));
// mock someOtherFoo() to ensure it gets the correct value for $arg
$fixture->expects($this->once())
->method('someOtherFoo')
->with('foo');
// call someFoo() with the correct $arg
$fixture->someFoo('foo');
}
Ответ 2
Вы можете поставить префикс имени вашего псевдонима с помощью overload:
Проверьте документы на насмешливых жестких зависимостей.
Ваш пример будет что-то вроде:
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class SomeClassTest extends \PHPUnit\Framework\TestCase
{
public function test_some_foo()
{
$someOtherClassMock = \Mockery::mock('overload:SomeOtherClass');
$someOtherClassMock->shouldReceive('someOtherFoo')
->once()
->with('something')
->andReturn();
$systemUnderTest = new SomeClass();
$systemUnderTest->someFoo('something');
}
}
Я добавил аннотацию @runTestsInSeparateProcesses
потому что обычно @runTestsInSeparateProcesses
класс будет использоваться и в других тестах. Без аннотации произойдет сбой автозагрузчика, поскольку в class already exists
ошибка.
Если это единственное место, где макетируемый класс используется в вашем наборе тестов, то вы должны удалить аннотацию.
Ответ 3
Я нашел свой путь здесь, пытаясь выполнить white-box тест класса __constructor, чтобы убедиться, что он вызывает метод класса сам по себе, а некоторые данные передаются в __constructor.
В случае, если кто-то другой по той же причине, я думал, что поделился бы тем методом, в котором я использовал (без метода factory -style createSomeClass(), используемого в этом вопросе).
<?php
class someClass {
public function __constructor($param1) {
// here is the method in the constructor we want to call
$this->someOtherFoo($param1);
}
public function someOtherFoo($var){ }
}
Теперь тест PHPUnit:
<?php
$paramData = 'someData';
// set up the mock class here
$model = $this->getMock('someClass',
array('someOtherFoo'), // override the method we want to check
array($paramData) // we need to pass in a parameter to the __constructor
);
// test that someOtherFoo() is called once, with out test data
$model->expects($this->once())
->with($paramData)
->method('someOtherFoo');
// directly call the constructor, instead of doing "new someClass" like normal
$model->__construct($paramData);