Как смоделировать php://ввод в PHP?
Я пишу unit test для моего проекта PHP,
unit test предназначен для моделирования данных php://input
,
и я прочитал руководство, в нем говорится:
php://input - поток, доступный только для чтения, который позволяет вам читать исходные данные из органа запроса.
Как смоделировать php://input
или написать тело запроса в моем PHP?
Здесь мой исходный код и unit test, оба упрощены.
Источник
class Koru
{
static function build()
{
// This function will build an array from the php://input.
parse_str(file_get_contents('php://input'), $input);
return $input;
}
//...
Unit Test
function testBuildInput()
{
// Trying to simulate the `php://input` data here.
// NOTICE: THIS WON'T WORK.
file_put_contents('php://input', 'test1=foobar&test2=helloWorld');
$data = Koru::build();
$this->assertEquals($data, ['test1' => 'foobar',
'test2' => 'helloWorld']);
}
Ответы
Ответ 1
Смотрите пакет vfsStream и этот вопрос и ответы.
В принципе, вы захотите параметризовать свою службу, которая считывает данные, чтобы принять путь:
public function __construct($path)
{
$data = file_get_contents($path); // you might want to use another FS read function here
}
И затем, в тесте, укажите путь потока vfsStream:
\vfsStreamWrapper::register();
\vfsStream::setup('input');
$service = new Service('vfs://input')
В вашем коде вы бы указали php://input
как обычно.
Ответ 2
Используйте двойной тест
Учитывая код в вопросе, самым простым решением является реструктуризация кода:
class Koru
{
static function build()
{
parse_str(static::getInputStream(), $input);
return $input;
}
/**
* Note: Prior to PHP 5.6, a stream opened with php://input could
* only be read once;
*
* @see http://php.net/manual/en/wrappers.php.php
*/
protected static function getInputStream()
{
return file_get_contents('php://input');
}
И используйте двойной тест:
class KoruTestDouble extends Koru
{
protected static $inputStream;
public static function setInputStream($input = '')
{
static::$inputStream = $input;
}
protected static function getInputStream()
{
return static::$inputStream;
}
}
Затем тестовый метод использует двойной тест, а не сам класс:
function testBuildInput()
{
KoruTestDouble::setInputStream('test1=foobar&test2=helloWorld');
$expected = ['test1' => 'foobar', 'test2' => 'helloWorld'];
$result = KoruTestDouble::build();
$this->assertSame($expected, $result, 'Stuff be different');
}
Избегайте статических классов, если возможно
Большинство трудностей с сценарием в вопросе вызваны использованием статических методов класса, статические классы делают тестирование сложным. Если это вообще возможно, избегайте использования статических классов и используйте методы экземпляра, которые позволяют решить такую же проблему, используя mock objects.
Ответ 3
Такая экстремальная декомпозиция ничего не выигрывает и ведет очень хрупкий код. Ваши тесты должны выражать ожидания ваших интерфейсов, а не данные, которые вы им предоставили: действительно ли PHP не может возвращать ["test2"=>"helloWorld","test1"=>"foobar"]
в какой-то будущей версии? Является ли ваш код сломанным, если это так? Как вы думаете, что вы тестируете?
Я думаю, что вы это слишком смущаете.
$a->doit
должен принимать $input
как аргумент и не вызывать Koru::build
как часть его инициализации. Затем вы можете протестировать $a->doit
вместо тестирования parse_str.
Если вы настаиваете на нажатии этого примера, то Koru::build
необходимо принять аргумент 'php://input'
- это часто называют инъекцией зависимостей, где вы сообщаете своим функциям все, что им нужно знать. Затем, когда вы хотите "протестировать" вещи, вы можете просто передать какой-нибудь другой файл (или, например, URL-адрес данных).
Ответ 4
С Kahlan вы можете обезвредить функцию file_get_contents
прямо так:
use My\Name\Space\Koru;
describe("::build()", function() {
it("parses data", function() {
allow('file_put_contents')->toBeCalled()->andRun(function() {
return 'test1=foobar&test2=helloWorld';
});
expect(Koru::build())->toBe([
'test1' => 'foobar',
'test2' => 'helloWorld'
]);
});
});
Ответ 5
Используйте Zend\Diactoros\Stream
https://zendframework.github.io/zend-diactoros/usage/
$_POST['foo'] = 'bar';
use Zend\Diactoros\ServerRequestFactory;
$psrRequest = ServerRequestFactory::fromGlobals();
var_dump($psrRequest->getParsedBody()); // foo => bar
var_dump($_POST); // foo => bar
Дополнительная информация https://laracasts.com/discuss/channels/general-discussion/psr-7?page=1