Имитация статического метода в том же классе (Mockery, Laravel9)

Я пишу модульный тест для проекта 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()...

У кого-нибудь есть идеи?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
0
0
152
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Два ваших теста содержат одну и ту же проблему, вы не использовали приложение 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');`

Спасибо за ваш ответ. Спасибо за объяснение, многого не знал. Я только что попробовал ваш код, я получил следующую ошибку. Получено Mockery_2_App_Models_Foo::testMethod(), но ожидания не указаны. Думаю, это связано с тем, что параметр shouldReceive не установлен. Но я хочу использовать исходный метод, не издеваясь над testMethod(). Если вы не возражаете, не могли бы вы сообщить нам, как мы можем помочь?

anko 26.04.2023 05:44

@anko Это не ошибка, если вы не определили функцию в shouldReceive, но все еще вызываете, Mockery выдаст исключение, чтобы сообщить вам, что это используется для предотвращения неожиданных вызовов функций.

Charlie 26.04.2023 07:46

Что касается «Я хочу использовать оригинальный метод, не издеваясь над testMethod ()», пожалуйста, дайте какой-нибудь тестовый пример, или я не знаю, чего вы на самом деле ожидаете.

Charlie 26.04.2023 07:50

Еще раз спасибо. Я имел в виду, что не хотел насмехаться, потому что тестируемый метод — это testMethod(). Извините, если я не понял цели вашего вопроса.

anko 26.04.2023 08:03

@anko Извините, я не могу понять ... Если вы не хотите использовать насмешки, просто удалите весь код и используйте $foo = new Foo(), $foo->testMethod('test argument'); как исходный код раньше.

Charlie 26.04.2023 08:26

Извините, я не могу объяснить это лучше, спасибо вам снова и снова!

anko 26.04.2023 08:43
Ответ принят как подходящий

Вы почти у цели, ваша проблема с новым кодом заключается в том, что вы не запрашиваете частичный макет, поэтому все, что вы не имитировали, вернет ошибку (не определено). Но нужно еще и перегружаться.

Я поделюсь решением, быстрым исправлением для вашего кода:

$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()»? (Мои права редактирования ограничены только тестовым кодом, поэтому я надеялся справиться с этим, просто тестируя как можно больше.)

anko 27.04.2023 12:33

Поскольку реальный код расширяет Model, я использовал его в качестве образца. Да, я буду осторожен. Спасибо!

anko 27.04.2023 14:11

Вы должны использовать static иначе тест никогда не пройдет, это просто для теста

matiaslauriti 27.04.2023 14:54

Мои извинения, сэр. Я отметил это ;) "иначе тест никогда не пройдет" Я очень хорошо понял, спасибо большое!

anko 28.04.2023 00:03

Другие вопросы по теме