Я пишу модульный тест для проекта Laravel.
Я хотел бы протестировать общедоступный метод testMethod() в следующем классе.
class Foo extends Model
{
public static function staticMethod($arg)
{
return 'from staticMethod';
}
public function testMethod($arg)
{
$result = self::staticMethod($arg);
return $result;
}
}
Вот мой тестовый код:
Редактировать 2
Следуя совету @matiaslauriti, я переписал тестовый код следующим образом.
//I did not use 'overload:'. When I used it, I got the error 'testMethod() does not exist on this mock object' (even with makePartial).
$mock = \Mockery::mock(Foo::class)->makePartial();
$mock->shouldReceive('staticMethod')
->once()
->andReturn('mocked return value');
$this->app->instance(Foo::class, $mock);
$mockedFoo = app(Foo::class);
$result = $mockedFoo->testMethod('test argument');
Затем я изменил self:: на static:: как и советовали.
public function testMethod($arg)
{
return static::staticMethod($arg);
}
Когда мы это сделали, тест прошел успешно, и мы смогли имитировать статические методы!!!!!
Изменить 1
Следуя совету @Charlie, я переписал тестовый код следующим образом.
$mock = \Mockery::mock(Foo::class);
$mock->shouldReceive('staticMethod')
->once()
->andReturn('mocked return value');
$this->app->instance(Foo::class, $mock);
$mockedFoo = app(Foo::class);
$result = $mockedFoo->testMethod('test argument');
Вот один вопрос.
// return 'mocked return value'
$mockedFoo->staticMethod('test');
// return value of original method.
Foo::staticMethod('test');
В тестируемом коде я вызываю его с помощью Foo::staticMethod(), а не экземпляра.
Я все еще пробую этот код, но можно ли издеваться над статическим методом без перегрузки?
И этот тест возвращается;
Mockery\Exception\BadMethodCallException: получен Mockery_2_App_Models_Foo::testMethod(), но ожидания не указаны
Эта ошибка означает, что макет распознает testMethod() (я рад!).
Но я хочу, чтобы testMethod() работал в соответствии с исходным кодом, поэтому я не хочу устанавливать возвращаемое значение для макета.
Итак, я использовал makePartial().
$mock = \Mockery::mock('overload:'.Foo::class)->makePartial();
Затем, с этого момента, макет staticMethod() не использовался при запуске теста, а использовался оригинальный staticMethod()...
У кого-нибудь есть идеи?






Два ваших теста содержат одну и ту же проблему, вы не использовали приложение Laravel для получения экземпляра класса, когда вы используете new Foo(), он фактически создал реальный экземпляр Foo:: class вместо издевательства над классом.
Непроверенный пример:
$mock = Mockery::mock(Foo::class);
$mock->shouldReceive('staticMethod')->once()->with('test argument')->andReturn('mocked return value');
// This line will replace the origin Foo::class with the mocked Foo::class.
$this->app->instance(Foo::class, $mock);
// $foo = new Foo(); Instantiate like this won't use the mocked Foo::class.
// Use "app(Foo::class)" to instantiate the mocked class.
$mockedFoo = $this->app(Foo::class);
$result = $mockedFoo->testMethod('test argument');`
@anko Это не ошибка, если вы не определили функцию в shouldReceive, но все еще вызываете, Mockery выдаст исключение, чтобы сообщить вам, что это используется для предотвращения неожиданных вызовов функций.
Что касается «Я хочу использовать оригинальный метод, не издеваясь над testMethod ()», пожалуйста, дайте какой-нибудь тестовый пример, или я не знаю, чего вы на самом деле ожидаете.
Еще раз спасибо. Я имел в виду, что не хотел насмехаться, потому что тестируемый метод — это testMethod(). Извините, если я не понял цели вашего вопроса.
@anko Извините, я не могу понять ... Если вы не хотите использовать насмешки, просто удалите весь код и используйте $foo = new Foo(), $foo->testMethod('test argument'); как исходный код раньше.
Извините, я не могу объяснить это лучше, спасибо вам снова и снова!
Вы почти у цели, ваша проблема с новым кодом заключается в том, что вы не запрашиваете частичный макет, поэтому все, что вы не имитировали, вернет ошибку (не определено). Но нужно еще и перегружаться.
Я поделюсь решением, быстрым исправлением для вашего кода:
$mock = \Mockery::mock('overload:'.Foo::class)->makePartial();
$mock->shouldReceive('staticMethod')
->once()
->andReturn('mocked return value');
$this->app->instance(Foo::class, $mock);
$mockedFoo = app(Foo::class); // This part makes no sense on the test, as you already have the $mock, so directly call the mock
$result = $mockedFoo->testMethod('test argument');
Но ваш исходный код нужно изменить, вместо self::staticMethod($arg) он должен быть static::staticMethod($arg), поэтому, когда Mockery перегружает класс Foo, чтобы он мог имитировать статический вызов, static относится к позднему связыванию (Mockery делает что-то вроде MockeryClass1 extends Foo, поэтому, если у вас есть self, он всегда будет ссылаться на Foo, так что никакого макета, но static всегда будет ссылаться на MockeryClass1 в этом случае, поэтому метод статического макета будет работать).
Итак, с точки зрения кода, Mockery делает что-то вроде этого:
// When you do this
$mock = \Mockery::mock('overload:'.Foo::class)->makePartial();
// Mockery does something like
class Mocker_class_fake_1 extends Foo
{
// ...
public function __call()
{
// ...
}
public function __callStatic()
{
// ...
}
}
/**
* So when you do $mock->shouldReceive('xyz') and then $mock->xyz()
* Mockery will use PHP __call ($this->method) magic method to resolve
* what to do.
*
* __callStatic does the same but for static calls. (Class::method)
*
* Having makePartial() does the same, only that will use original code
* instead of erroring that there is no definition for that method call.
*/
Теперь, если ваш код:
public function testMethod($arg)
{
return self::staticMethod($arg);
}
При использовании этого Mocker_class_fake_1 примера класса self разрешается в Foo ВСЕГДА, но static разрешается в тот, который расширяет и вызывает этот метод, в этом случае Mocker_class_fake_1::staticMethod($arg), а затем __callStatic сможет разрешить staticMethod, потому что вы написали shouldReceive('staticMethod') и т. д.
Но вам нужны 3 вещи:
overload:.Foo::классmakePartial()$this->app->instance(Foo::class, $mock); и app(Foo::class); (или resolve(Foo::class);, точный псевдоним app(...))Мой вопрос будет заключаться в том, почему вы расширяете модель и тестируете ее, обычно вы не смешиваете это (конечно, вы можете, но это плохая практика). Поэтому я бы создал новый класс домена и отделил логику модели от логики домена, но я не уверен, почему у вас это так, поскольку вы просто помещаете пример кода, а не реальный код.
Надеюсь, это прояснит больше!
Подробнее о __call и __callStatic.
Большое спасибо @matiaslauriti !! Я последовал вашему совету, переписал его и смог успешно запустить тест! (Я включил переписанный код в Edit2 поста.) Спасибо, что так много поделились с нами. У меня есть еще один вопрос. Требуется ли в этом случае изменение на «static::staticMethod()»? (Мои права редактирования ограничены только тестовым кодом, поэтому я надеялся справиться с этим, просто тестируя как можно больше.)
Поскольку реальный код расширяет Model, я использовал его в качестве образца. Да, я буду осторожен. Спасибо!
Вы должны использовать static иначе тест никогда не пройдет, это просто для теста
Мои извинения, сэр. Я отметил это ;) "иначе тест никогда не пройдет" Я очень хорошо понял, спасибо большое!
Спасибо за ваш ответ. Спасибо за объяснение, многого не знал. Я только что попробовал ваш код, я получил следующую ошибку. Получено Mockery_2_App_Models_Foo::testMethod(), но ожидания не указаны. Думаю, это связано с тем, что параметр shouldReceive не установлен. Но я хочу использовать исходный метод, не издеваясь над testMethod(). Если вы не возражаете, не могли бы вы сообщить нам, как мы можем помочь?