Mock harus dipanggil tepat 1 kali tetapi dipanggil 0 kali

Saya punya masalah aneh dengan Laravel 5 dan PHPUnit. Ketika saya mencoba meniru fasad Laravel (mis. Auth, View, Mail) saya selalu mendapatkan pengecualian ini:

Mockery\Exception\InvalidCountException: Metode send(emails.register, array('user'=›'object(MCC\Models\Users\User)',), object(Closure)) dari Mockery_0_Illuminate_Mail_Mailer harus dipanggil tepat 1 kali tetapi dipanggil 0 kali.

Saya punya masalah dengan bagian "should be called exactly 1 times but called 0 times.". Ini adalah kode pengujian saya:

public function testSendEmailToNewUserListener()
{
    $user = factory(MCC\Models\Users\User::class)->create();

    Mail::shouldReceive('send')
        ->with(
            'emails.register',
            ['user' => $user],
            function ($mail) use ($user) {
               $mail->to($user->email, $user->name)
                    ->subject('Thank you for registering an account.');
            }
        )
        ->times(1)
        ->andReturnUsing(function ($message) use ($user) {
            dd($message);
            $this->assertEquals('Thank you for registering an account.', $message->getSubject());
            $this->assertEquals('mcc', $message->getTo());
            $this->assertEquals(View::make('emails.register'), $message->getBody());
        });
}

Saya menutup dd($message) karena saya ingin mengetahui detail tentang nilai pengembalian (tampilan $message->getTo()).

Kelas TestCase saya:

<?php

/**
 * Provides default values for functional tests.
 *
 * Class TestCase
 */
abstract class TestCase extends Illuminate\Foundation\Testing\TestCase
{
    /**
     * The base URL to use while testing the application.
     *
     * @var string
     */
    protected $baseUrl = 'http://004-mcc.dev';

    /**
     * Creates the application.
     *
     * @return \Illuminate\Foundation\Application
     */
    public function createApplication()
    {
        $app = require __DIR__ . '/../bootstrap/app.php';

        $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();

        \Illuminate\Support\Facades\Mail::pretend(TRUE);

        return $app;
    }
}

Phpunit.xml saya:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
     backupStaticAttributes="false"
     bootstrap="bootstrap/autoload.php"
     colors="true"
     convertErrorsToExceptions="true"
     convertNoticesToExceptions="true"
     convertWarningsToExceptions="true"
     processIsolation="false"
     stopOnFailure="false"
     syntaxCheck="false">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>./tests/</directory>
        </testsuite>
        <testsuite name="User">
            <directory>./tests/UserRepository</directory>
        </testsuite>
        <testsuite name="User/Auth">
            <directory>./tests/UserRepository/Auth</directory>
        </testsuite>
        <testsuite name="User/User">
            <directory>./tests/UserRepository/User</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">app/</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="local"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
    </php>
</phpunit>

Saya memeriksa banyak sumber dari Google, dari Stackoverflow, yang disebutkan kebanyakan orang

$this->app->instance('Illuminate\Mail\Mailer', $mockMailer)

tetapi bahkan instruksi ini tidak membantu. Sebagian besar pertanyaan tentang masalah ini tidak terpecahkan. Saya memeriksa ekstensi yang terpasang, Laravel saya baru diinstal (beberapa model, beberapa rute, sekitar 20 tes).

Saya juga mencoba metode seperti

->atLeast()
->times(1)

or

->atLeast()
->once()

tapi tidak ada yang berfungsi dengan baik. Juga sebagai gantinya

Mail::shouldReceive('mail')

saya menggunakan

$mailMock = Mockery::mock('Illuminate\Mail\Mailer');
$mailMock->shouldReceive('mail)

tetapi cara-cara ini masih tidak berhasil.

Log konsol lainnya:

/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery/CountValidator/Exact.php:37
/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery/Expectation.php:271
/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:120
/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery/Container.php:297
/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery/Container.php:282
/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery.php:142
/home/grzgajda/programowanie/php/005mcc/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:48
/home/grzgajda/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php:148
/home/grzgajda/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php:100

Saya juga menemukan satu saran bagus (di sana Stackoverflow) tetapi tidak berhasil.

Mockery secara default adalah perpustakaan yang mematikan, bukan perpustakaan yang mengejek (yang membingungkan karena namanya).

Artinya -›shouldReceive(...) secara default adalah nol kali atau lebih. Saat menggunakan -›once(), Anda mengatakan itu harus dipanggil nol atau satu kali, tetapi tidak lebih. Artinya, hal itu akan selalu berlalu.

Saat Anda ingin menegaskan bahwa ini dipanggil satu kali, Anda dapat menggunakan -›atLeast()-›times(1) (satu kali atau lebih) atau -›times(1) (tepat satu kali)

Versi php saya: PHP 5.6.14-1+deb.sury.org~trusty+1 (cli)

Apache saya: Server version: Apache/2.4.16 (Ubuntu)

Versi ejekan (dari komposer): "mockery/mockery": "0.9.*"

Kerangka kerja Laravel (dari komposer): "laravel/framework": "5.1.*"


person Grzegorz Gajda    schedule 11.10.2015    source sumber


Jawaban (2)


Melihat kasus uji Anda:

public function testSendEmailToNewUserListener()
{
    $user = factory(MCC\Models\Users\User::class)->create();

    Mail::shouldReceive('send')
        ->with(
            'emails.register',
            ['user' => $user],
            function ($mail) use ($user) {
               $mail->to($user->email, $user->name)
                    ->subject('Thank you for registering an account.');
            }
        )
        ->times(1)
        ->andReturnUsing(function ($message) use ($user) {
            dd($message);
            $this->assertEquals('Thank you for registering an account.', $message->getSubject());
            $this->assertEquals('mcc', $message->getTo());
            $this->assertEquals(View::make('emails.register'), $message->getBody());
        });
}

Salah satu:

  • Membuat pengguna akan memanggil fasad Mail, dalam hal ini Anda memanggil fasad tersebut sebelum Anda mengejeknya.

OR

  • Anda tidak memanggil fungsi yang memanggil fasad Mail.

Apa pun yang terjadi, Mail::shouldReceive('send') seharusnya tidak menjadi hal terakhir dalam kasus pengujian.

Kesalahan yang Anda dapatkan adalah karena Mail mengharapkan panggilan terjadi setelah Anda memanggil ::shouldRecieve(), namun ternyata tidak - kasus uji berakhir, dan instance Mockery tidak pernah dipanggil.

Anda dapat mencoba sesuatu seperti ini:

public function testSendEmailToNewUserListener()
{   
    $testCase = $this;

    Mail::shouldReceive('send')
        ->times(1)
        ->andReturnUsing(function ($message) use ($testCase) {
            $testCase->assertEquals('Thank you for registering an account.', $message->getSubject());
            $testCase->assertEquals('mcc', $message->getTo());
            $testCase->assertEquals(View::make('emails.register'), $message->getBody());
        });

    $user = factory(MCC\Models\Users\User::class)->create();
}
person samlev    schedule 20.10.2015

Untuk mengintegrasikan Mockery dengan PhpUnit, Anda hanya perlu mendefinisikan metode air mata() untuk pengujian Anda yang berisi hal berikut:

public function tearDown() {
    \Mockery::close();
}

Panggilan statis ini membersihkan wadah Mockery yang digunakan oleh pengujian saat ini, dan menjalankan tugas verifikasi apa pun yang diperlukan sesuai harapan Anda.

Anda dapat menemukan informasi lebih lanjut tentang integrasi Mockery dan PhpUnit di sini.

person Alex Kyriakidis    schedule 12.10.2015
comment
Metode bawaan Laravel untuk tearDown() memiliki Mockery::close(); dan masih tidak membantu. Mewarisi metode induk tearDown() dan menambahkan fungsi ke awal (atau akhir) juga tidak membantu. Dari dokumen Mockery saya mengambil pendengar dan menempatkannya di phpunit.xml saya dan masih tidak berhasil. Masih pengecualian yang sama. - person Grzegorz Gajda; 12.10.2015
comment
Sudahkah Anda mencoba menutup ejekan tanpa mewarisi kedekatan orang tua? - person Alex Kyriakidis; 12.10.2015
comment
ya, dalam banyak kombinasi. Saya menempatkan Mockery::close() di akhir metode, custom tearDown() di kelas TestCase, custom tearDown() di kelas pengujian dan tentu saja mencampurkan kombinasi ini. Masih pengecualian yang sama. - person Grzegorz Gajda; 12.10.2015