Laravel测试失败,出现“ ... ReflectionException:类配置不存在...”

问题描述

搜索了StackOverflow,然后网络尝试了我发现的所有内容,以解决以下问题,但无济于事。该错误仅在PHPunit测试期间发生。如果运行Laravel应用程序(PHP artisan服务),则可以正常运行。

以下是详细信息:

我正在运行Win7的cygwin。我使用的是在PHP artisan create项目中创建的composer.json。

Cygwin (win 7)
Runtime: PHP 7.4.8
PHPUnit 9.3.7 by Sebastian Bergmann and contributors.
Laravel Framework 7.26.0

这是我的composer.json,我认为它最初是由“ PHP artisan create project”或类似的东西创建的。

$ cat composer.json
{
    "name": "rubens-gomes/rubens-gomes.com","type": "project","description": "rubens-gomes.com Web Site","homepage": "https://rubens-gomes.com","keywords": [
        "framework","laravel"
    ],"license": "PROPRIETARY","authors": [
        {
            "name": "Rubens Gomes","email": "rubens.s.gomes@gmail.com","role": "Developer"
        }
    ],"support": {
        "email": "rubens.s.gomes@gmail.com"
    },"require": {
        "PHP": ">=7.4","fideloper/proxy": "^4.2","fruitcake/laravel-cors": "^2.0","google/recaptcha": "^1.2","guzzlehttp/guzzle": "^6.3","laravel/framework": "^7.24","laravel/tinker": "^2.0"
    },"require-dev": {
        "facade/ignition": "^2.0","fzaninotto/faker": "^1.9.1","mockery/mockery": "^1.3.1","nunomaduro/collision": "^4.1","PHPunit/PHPunit": "^9.2"
    },"config": {
        "optimize-autoloader": true,"preferred-install": "dist","sort-packages": true
    },"extra": {
        "laravel": {
            "dont-discover": []
        }
    },"autoload": {
        "psr-4": {
            "App\\": "app/"
        },"classmap": [
            "database/seeds","database/factories"
        ]
    },"autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    },"minimum-stability": "dev","prefer-stable": true,"scripts": {
        "post-autoload-dump": [
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump","@PHP artisan package:discover --ansi"
        ],"post-root-package-install": [
            "@PHP -r \"file_exists('.env') || copy('.env.example','.env');\""
        ],"post-create-project-cmd": [
            "@PHP artisan key:generate --ansi"
        ],"post-update-cmd": "@composer dump-autoload"
    }
}

下面是我的PHPunit.xml。我尝试了不同的引导程序,但无济于事。

<?xml version="1.0" encoding="UTF-8"?>
<PHPunit
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="https://schema.PHPunit.de/9.3/PHPunit.xsd"
  <!--  bootstrap="vendor/autoload.PHP" -->
  bootstrap="bootstrap/app.PHP"
  colors="true"
  verbose="true">
  <coverage processUncoveredFiles="true">
    <include>
      <directory suffix=".PHP">./app</directory>
    </include>
  </coverage>
  <testsuites>
    <testsuite name="Unit">
      <directory suffix="Test.PHP">./tests/Unit</directory>
    </testsuite>
    <testsuite name="Feature">
      <directory suffix="Test.PHP">./tests/Feature</directory>
    </testsuite>
  </testsuites>
  <PHP>
    <server name="APP_ENV" value="testing"/>
    <server name="BCRYPT_ROUNDS" value="4"/>
    <server name="CACHE_DRIVER" value="array"/>
    <server name="MAIL_MAILER" value="array"/>
    <server name="QUEUE_CONNECTION" value="sync"/>
    <server name="SESSION_DRIVER" value="array"/>
    <server name="TELEScopE_ENABLED" value="false"/>
  </PHP>
</PHPunit>

这是PHPunit致命错误。我不太担心下面的测试用例失败。主要问题是有关“未找到配置...”的致命错误

$ PHP artisan test
Warning: TTY mode is not supported on Windows platform.


   PASS  Tests\Unit\ExampleTest
  ✓ basic test


   FAIL  Tests\Unit\app\Http\Controllers\ContactControllerTest
  ✕ fail to validate email

  Tests:  1 Failed,1 passed,1 pending

  Failed asserting that exception of type "ErrorException" matches expected exception "Illuminate\Validation\ValidationException". Message was: "dns_get_record(): DNS Query Failed" at D:\gitprojs\PHP\rubens-gomes\vendor\egulias\email-validator\src\Validation\DNSCheckValidation.PHP:121 D:\gitprojs\PHP\rubens-gomes\vendor\egulias\email-validator\src\Validation\DNSCheckValidation.PHP:107 D:\gitprojs\PHP\rubens-gomes\vendor\egulias\email-validator\src\Validation\DNSCheckValidation.PHP:80 D:\gitprojs\PHP\rubens-gomes\vendor\egulias\email-validator\src\Validation\MultipleValidationWithAnd.PHP:65 D:\gitprojs\PHP\rubens-gomes\vendor\egulias\email-validator\src\EmailValidator.PHP:37 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Validation\Concerns\ValidatesAttributes.PHP:666 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Validation\Validator.PHP:547 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Validation\Validator.PHP:370 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Validation\Validator.PHP:401 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Validation\Validator.PHP:444 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Validation\Factory.PHP:136 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Foundation\Providers\FoundationServiceProvider.PHP:58 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Support\Traits\Macroable.PHP:111 D:\gitprojs\PHP\rubens-gomes\app\Http\Controllers\ContactController.PHP:90 D:\gitprojs\PHP\rubens-gomes\tests\Unit\app\Http\Controllers\ContactControllerTest.PHP:43 .

  at D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\Framework\Constraint\Constraint.PHP:121
    117|         if (!empty($description)) {
    118|             $failureDescription = $description . "\n" . $failureDescription;
    119|         }
    120|
  > 121|         throw new ExpectationFailedException(
    122|             $failureDescription,123|             $comparisonFailure
    124|         );
    125|     }

  1   D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\Framework\Constraint\Constraint.PHP:55
      PHPUnit\Framework\Constraint\Constraint::fail(Object(ErrorException),"")

  2   D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\Framework\Assert.PHP:2331
      PHPUnit\Framework\Constraint\Constraint::evaluate(Object(ErrorException),"")
PHP Fatal error:  Uncaught ReflectionException: Class config does not exist in D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Container\Container.PHP:809
Stack trace:
#0 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Container\Container.PHP(809): ReflectionClass->__construct('config')
#1 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Container\Container.PHP(691): Illuminate\Container\Container->build('config')
#2 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Foundation\Application.PHP(796): Illuminate\Container\Container->resolve('config',Array,true)
#3 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Container\Container.PHP(637): Illuminate\Foundation\Application->resolve('config',Array)
#4 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Foundation\Application.PHP(781): Illuminate\Container\Container->make('config',Array)
#5 D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\C in D:\gitprojs\PHP\rubens-gomes\vendor\laravel\framework\src\Illuminate\Container\Container.PHP on line 811
PHP Stack trace:
PHP   1. {main}() D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\PHPunit:0
PHP   2. PHPUnit\TextUI\Command::main($exit = *uninitialized*) D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\PHPunit:61
PHP   3. PHPUnit\TextUI\Command->run($argv = *uninitialized*,$exit = *uninitialized*) D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\TextUI\Command.PHP:100
PHP   4. PHPUnit\TextUI\TestRunner->run($suite = *uninitialized*,$arguments = *uninitialized*,$warnings = *uninitialized*,$exit = *uninitialized*) D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\TextUI\Command.PHP:147
PHP   5. PHPUnit\Framework\TestSuite->run($result = *uninitialized*) D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\TextUI\TestRunner.PHP:671
PHP   6. PHPUnit\Framework\TestSuite->run($result = *uninitialized*) D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\Framework\TestSuite.PHP:665
PHP   7. PHPUnit\Framework\TestSuite->run($result = *uninitialized*) D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\Framework\TestSuite.PHP:665
PHP   8. Tests\Unit\app\Http\Controllers\ContactControllerTest->run($result = *uninitialized*) D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\Framework\TestSuite.PHP:665
PHP   9. PHPUnit\Framework\TestResult->run($test = *uninitialized*) D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\Framework\TestCase.PHP:880
PHP  10. PHPUnit\Framework\TestResult->addFailure($test = *uninitialized*,$e = *uninitialized*,$time = *uninitialized*) D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\Framework\TestResult.PHP:894
PHP  11. NunoMaduro\Collision\Adapters\PHPunit\Printer->addFailure($testCase = *uninitialized*,$error = *uninitialized*,$time = *uninitialized*) D:\gitprojs\PHP\rubens-gomes\vendor\PHPunit\PHPunit\src\Framework\TestResult.PHP:391
PHP  12. NunoMaduro\Collision\Adapters\PHPunit\Style->writeError($state = *uninitialized*,$throwable = *uninitialized*) D:\gitprojs\PHP\rubens-gomes\vendor\nunomaduro\collision\src\Adapters\PHPunit\PrinterContents.PHP:114

下面是正在测试的控制器。注意,这是非常基本的代码,它只是为了检查事情是否正常进行测试。

顺便说一句,我正在学习Laravel。

$ cat app/Http/Controllers/ContactController.PHP
<?PHP
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\MessageBag;
use Illuminate\Support\Facades\Log;
use ReCaptcha\ReCaptcha;

/**
 * Controller to handle incoming "/contact" form requests.
 *
 * @author Rubens Gomes <rubens.s.gomes@gmail.com>
 * @copyright 2020 Rubens Gomes
 */
final class ContactController extends Controller
{

    /** @var array containing fields to be used on the view.*/
    private $data;

    /** @var array contact form validation rules. */
    private $formRules;

    /**
     *
     * @var \ReCaptcha\ReCaptcha instance of Google ReCaptcha to be injected
     *      during unit testing. If no instance is injected a new one is created.
     */
    private $recaptcha;

    /**
     * The default constructor.
     *
     * @param ReCaptcha $rc The Google ReCaptcha object.
     */
    public function __construct(ReCaptcha $rc = null)
    {
        if ($rc === null) {
            $this->recaptcha = new ReCaptcha(config('services.recaptcha.secret_key'));
        } else {
            $this->recaptcha = $rc;
        }

        $this->data = [
            'pageId' => 'contact','homeHref' => '/#home','aboutHref' => '/#about','contactHref' => '#contact'
        ];

        $this->formRules = [
            'email' => 'required|email:dns','name' => 'required','msg' => 'required','g-recaptcha-response' => 'required'
        ];
        Log::debug(__CLASS__ . " constructed");
    }

    public function __destruct()
    {
        Log::debug(__CLASS__ . " destructed");
    }

    /**
     * Process the incoming GET request to render the contact page.
     *
     * @return \Illuminate\View\View The contact main page view.
     */
    public function index()
    {
        Log::debug(__CLASS__ . ":" . __FUNCTION__);
        return view('contact',$this->data);
    }

    /**
     * Handles the contact message form POST submit by validating the form
     * fields,validating the captcha and then sending an email message with
     * the information provided in the form.
     *
     * @param \Illuminate\Http\Request $request
     *            The HTTP request
     * @return \Illuminate\View\View The contact main page view.
     */
    public function sendmsg(Request $request)
    {
        Log::debug(__CLASS__ . ":" . __FUNCTION__);
        // validate input contact fields
        Log::debug('validating contact form');
        $request->validate($this->formRules);

        // validate the captcha
        $captcha = $request->input(config('services.recaptcha.resp_parm'));
        Log::debug('validating captcha...');
        $response = $this->recaptcha->verify($captcha);

        if (! $response->isSuccess()) {
            Log::warning('Failed to authenticate captcha');
            $captchaError = new MessageBag();
            $captchaError->add('recaptcha_error','Failed to authenticate captcha');
            return view('contact',$this->data)->withErrors($captchaError);
        }

        return redirect()->back()->withSuccess('Your message has been successfully submitted.');
    }
}
/******************************************************************************/

// EOF

这是我的控制器测试代码

$ cat tests/Unit/app/Http/Controllers/ContactControllerTest.PHP
<?PHP
namespace Tests\Unit\app\Http\Controllers;

use App\Http\Controllers\ContactController;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;
use ReCaptcha\ReCaptcha;

class ContactControllerTest extends TestCase
{

    /** @var \App\Http\Controllers\ContactController The controller being tested */
    private $controller;

    /** @var ReCaptcha\ReCaptcha The Google ReCaptcha being doubled */
    private $rcStub;

    public function setUp(): void
    {
        parent::setUp();
        $this->rcStub = $this->createStub(ReCaptcha::class);
        $this->controller = new ContactController($this->rcStub);
    }

    public function tearDown(): void
    {
        parent::tearDown();
    }

    public function testFailTovalidateEmail()
    {
        $this->expectException(ValidationException::class);

        $form = [
            'email' => 'rubens.s.gomes @ gmail.com','msg' => 'This is a test.\nRubens Gomes','name' => 'Rubens+Gomes','g-recaptcha-response' => 'hello google captcha'
        ];

        $request = Request::create('/contact/sendmsg','POST',$form);
        $this->controller->sendmsg($request);
    }
}

这是我的.env文件

$ cat .env
APP_NAME="Laravel"
APP_ENV="local"
APP_KEY="base64:mJmYLlc2V43AKfa3u7s1po7RHyPDajRZIB4pIArTyjg="
APP_DEBUG=true
APP_URL="http://localhost"

LOG_CHANNEL="stack"

DB_CONNECTION="MysqL"
DB_HOST="127.0.0.1"
DB_PORT=3306
DB_DATABASE="laravel"
DB_USERNAME="root"
DB_PASSWORD="root"

broADCAST_DRIVER="log"
CACHE_DRIVER="file"
QUEUE_CONNECTION="sync"
SESSION_DRIVER="file"
SESSION_LIFETIME=120

REdis_HOST="127.0.0.1"
REdis_PASSWORD="null"
REdis_PORT=6379

MAIL_MAILER="smtp"
MAIL_HOST="smtp.mailtrap.io"
MAIL_PORT=2525
MAIL_USERNAME="null"
MAIL_PASSWORD="null"
MAIL_ENCRYPTION="null"
MAIL_FROM_ADDRESS="null"
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=""
AWS_SECRET_ACCESS_KEY=""
AWS_DEFAULT_REGION="us-east-1"
AWS_BUCKET=""

PUSHER_APP_ID=""
PUSHER_APP_KEY=""
PUSHER_APP_SECRET=""
PUSHER_APP_CLUSTER="mt1"

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

RECAPTCHA_DOMAIN="NOT_disPLAYED"
RECAPTCHA_SITE_KEY="NOT_disPLAYED"
RECAPTCHA_SECRET_KEY="NOT_disPLAYED"
RECAPTCHA_OWNER="rubens.s.gomes@gmail.com"
RECAPTCHA_VERSION="V2"

解决方法

我重写了上面的所有代码。现在工作正常。以下是一些新代码的一部分。

composer.json:

$ cat composer.json
{
        "name" : "rubens-gomes/rubens-gomes.com","type" : "project","description" : "rubens-gomes.com Web Site","homepage" : "https://rubens-gomes.com","keywords" : [
                "framework","laravel"
        ],"license" : "PROPRIETARY","authors" : [{
                        "name" : "Rubens Gomes","email" : "rubens.s.gomes@gmail.com","role" : "Developer"
                }
        ],"support" : {
                "email" : "rubens.s.gomes@gmail.com"
        },"require" : {
                "php" : ">=7.4","fideloper/proxy" : "^4.2","fruitcake/laravel-cors" : "^2.0","google/recaptcha" : "^1.2","guzzlehttp/guzzle" : "^6.3","laravel/framework" : "^7.24","laravel/tinker" : "^2.0"
        },"require-dev" : {
                "facade/ignition" : "^2.0","fzaninotto/faker" : "^1.9.1","mockery/mockery" : "^1.3.1","nunomaduro/collision" : "^4.1","phpunit/phpunit" : "^8.5"
        },"config" : {
                "optimize-autoloader" : true,"preferred-install" : "dist","sort-packages" : true
        },"extra" : {
                "laravel" : {}
        },"autoload" : {
                "psr-4" : {
                        "App\\" : "app/"
                },"classmap" : [
                        "database/seeds","database/factories"
                ]
        },"autoload-dev" : {
                "psr-4" : {
                        "Tests\\" : "tests/"
                }
        },"minimum-stability" : "dev","prefer-stable" : true,"scripts" : {
                "post-autoload-dump" : [
                        "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump","@php artisan package:discover --ansi"
                ],"post-root-package-install" : "@php -r \"file_exists('.env') || copy('.env.example','.env');\"","post-create-project-cmd" : "@php artisan key:generate --ansi","post-update-cmd" : "@composer dump-autoload"
        }
}

phpunit.xml:

$ cat phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
  To run the unit tests configured in this PHPUnit XML configuration file:

       php artisan test -vvv

   @author Rubens Gomes <rubens.s.gomes@gmail.com>
   @link https://rubens-gomes.com
-->
<phpunit
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
  bootstrap="bootstrap/app.php"
  colors="true"
  verbose="true">
  <testsuites>
    <testsuite name="Unit">
      <directory suffix="Test.php">./tests/Unit</directory>
    </testsuite>
    <testsuite name="Feature">
      <directory suffix="Test.php">./tests/Feature</directory>
    </testsuite>
  </testsuites>
  <php>
    <server name="APP_ENV" value="testing"/>
  </php>
</phpunit>

.env:

$ cat .env
APP_NAME="rubens-gomes.com"
APP_ENV="testing"
APP_KEY="base64:mJmYLlc2V43AKfa3u7s1po7RHyPDajRZIB4pIArTyjg="
APP_DEBUG=true
APP_URL="http://localhost:8080"

LOG_CHANNEL="stdout"

MAIL_MAILER="smtp"
MAIL_HOST="smtp.gmail.com"
MAIL_PORT=465
MAIL_USERNAME="NOT_DISPLAYED"
MAIL_PASSWORD="NOT_DISPLAYED"
MAIL_FROM_ADDRESS="NOT_DISPLAYED"
MAIL_FROM_NAME="${APP_NAME}"

RECAPTCHA_DOMAIN="rubens-gomes.com"
RECAPTCHA_SITE_KEY="NOD_DISPLAYED"
RECAPTCHA_SECRET_KEY="NOT_DISPLAYED"
RECAPTCHA_OWNER="rubens.s.gomes@gmail.com"
RECAPTCHA_VERSION="V2"

测试输出:

$ php artisan test -vvv
Warning: TTY mode is not supported on Windows platform.


   PASS  Tests\Unit\ExampleTest
  ✓ basic test




   RUNS  Tests\Unit\app\Http\Controllers\ContactControllerTest
  • fail due to invalid form

  Tests:  4 passed,17 pending
[2020-08-30T05:29:18.173039+00:00] testing.DEBUG: creating contact instance [] []

   RUNS  Tests\Unit\app\Http\Controllers\ContactControllerTest
  • ensure sucess response for valid form


   PASS  Tests\Unit\app\Http\Controllers\ContactControllerTest
  ✓ ensure request to index page works
  ✓ fails due to wrong resource
  ✓ fail due to post method not allowed for index page
  ✓ fail due to invalid form
  ✓ ensure sucess response for valid form



   PASS  Tests\Unit\app\Http\Controllers\HomeControllerTest
  ✓ ensure request to index page works
  ✓ fails due to wrong resource




   PASS  Tests\Unit\app\Mail\ContactRequestMailableTest
  ✓ fail due to null contact
  ✓ ensure constructor works
  ✓ ensure build works




   PASS  Tests\Unit\app\Mail\ContactResponseMailableTest
  ✓ fail due to null contact
  ✓ ensure constructor works
  ✓ ensure build works

   RUNS  Tests\Unit\app\Model\ContactTest
  • fail due to invalid email

   RUNS  Tests\Unit\app\Model\ContactTest
  • fail due to invalid email domain





   PASS  Tests\Unit\app\Model\ContactTest
  ✓ fail due to invalid email
  ✓ fail due to invalid email domain
  ✓ ensure contact is created
  ✓ ensure email is not empty
  ✓ ensure name is not empty
  ✓ ensure msg is not empty


   PASS  Tests\Feature\ExampleTest
  ✓ basic test

  Tests:  21 passed
  Time:   4.93s

ContactControllerTest.php:

<?php
namespace Tests\Unit\app\Http\Controllers;

use App\Http\Controllers\ContactController;
use App\Mail\ContactRequestMailable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use ReCaptcha\ReCaptcha;
use Tests\TestCase;

class ContactControllerTest extends TestCase
{

    /** @var \App\Http\Controllers\ContactController The controller being tested */
    private $controller;

    /** @var ReCaptcha\ReCaptcha The Google ReCaptcha being doubled */
    private $rcStub;

    /** @var array valid post form data for testing purposes */
    private const VALID_FORM = [
        'email' => 'rubens.s.gomes@gmail.com','msg' => 'This is a test.\nRubens Gomes','name' => 'Rubens+Gomes','g-recaptcha-response' => 'hello google captcha'
    ];

    /** @var array invalid post form data for testing purposes */
    private const INVALID_FORM = [
        'email' => 'rubens.s.gomes@gmail.com','name' => '','g-recaptcha-response' => 'hello google captcha'
    ];

    public function setUp(): void
    {
        parent::setUp();
        $this->rcStub = $this->createStub(ReCaptcha::class);
        $this->controller = new ContactController($this->rcStub,Mail::fake());
    }

    public function tearDown(): void
    {
        parent::tearDown();
    }

    public function testEnsureRequestToIndexPageWorks()
    {
        $response = $this->get('/contact/');
        $this->assertTrue($response->isOk());
    }

    public function testFailsDueToWrongResource()
    {
        $response = $this->get('/contact/hello');
        $response->assertStatus(404);
    }

    public function testFailDueToPostMethodNotAllowedForIndexPage()
    {
        $response = $this->post('/contact/');
        $response->assertStatus(405);
    }

    public function testFailDueToInvalidForm()
    {
        $response = $this->post('/contact/sendmsg',self::INVALID_FORM);
        $response->assertRedirect();
    }

    // unit testing controller
    public function testEnsureSucessResponseForValidForm()
    {
        $request = Request::create('/contact/sendmsg','POST',self::VALID_FORM);
        $rcResp = new \ReCaptcha\Response(TRUE);
        $this->rcStub->method('verify')->willReturn($rcResp);
        $this->controller->sendmsg($request);
        Mail::assertSent(ContactRequestMailable::class,1);
    }
}

ContactController.php

<?php
namespace App\Http\Controllers;

use App\Mail\ContactRequestMailable;
use App\Model\Contact;
use Illuminate\Http\Request;
use Illuminate\Support\MessageBag;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use ReCaptcha\ReCaptcha;

/**
 * Controller to handle incoming "/contact" form requests.
 *
 * @author Rubens Gomes <rubens.s.gomes@gmail.com>
 */
final class ContactController extends Controller
{

    /** @var array containing fields to be used in the view.*/
    private const VIEW_DATA = [
        'pageId' => 'contact','homeHref' => '/#home','aboutHref' => '/#about','contactHref' => '#contact'
    ];

    /** @var \Illuminate\Support\Facades\Mail instance of mail service. */
    private $mailer;

    /** @var \ReCaptcha\ReCaptcha instance of Google ReCaptcha. */
    private $recaptcha;

    /**
     * The default constructor.
     *
     * @param ReCaptcha $rc
     *            The Google ReCaptcha object.
     */
    public function __construct(ReCaptcha $rc = null)
    {
        if ($rc === null) {
            $this->recaptcha = new ReCaptcha(config('services.recaptcha.secret_key'));
        } else {
            // used during unit testing to inject a test double object
            $this->recaptcha = $rc;
        }
    }

    /**
     * Process the incoming GET request to render the contact page.
     *
     * @return \Illuminate\View\View The contact main page view.
     */
    public function index()
    {
        return view('contact',self::VIEW_DATA);
    }

    /**
     * Handles the contact message form POST submit by validating the form
     * fields,validating the captcha and then sending an email message with
     * the information provided in the form.
     *
     * @param \Illuminate\Http\Request $request
     *            The HTTP request
     * @return \Illuminate\View\View The contact main page view.
     */
    public function sendmsg(Request $request)
    {
        // validate and create contact
        $data = [
            'name' => $request->input('name'),'email' => $request->input('email'),'msg' => $request->input('msg')
        ];

        $contact = null;

        try {
            Log::debug('creating contact instance');
            $contact = new Contact($data['name'],$data['email'],$data['msg']);
        } catch (\InvalidArgumentException $ex) {
            Log::error('failed to create contact',$data);
            return redirect()->back()
                ->withException($ex)
                ->withErrors($ex->getMessage());
        }

        // validate the captcha
        $captcha = $request->input(config('services.recaptcha.resp_parm'));
        Log::debug('validating captcha',['captcha' => $captcha]);
        $response = $this->recaptcha->verify($captcha);

        if (! $response->isSuccess()) {
            Log::warning('failed to authenticate captcha',['captcha' => $captcha]);
            $captchaError = new MessageBag();
            $captchaError->add('recaptcha_error','Failed to authenticate captcha');
            return redirect()->back()->withErrors($captchaError);
        }

        // send message by email
        Log::debug('sending email',['email' => $contact->getEmail() ]);
        $reqMail = new ContactRequestMailable($contact);
        Mail::to($contact->getEmail())->send($reqMail);
        return redirect()->back()->withSuccess('Your message has been successfully submitted.');
    }
}
/******************************************************************************/

// EOF

Contact.php:

<?php
namespace App\Model;

use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;

/**
 * A data model object to represent a contact (e.g.,from the contact form).
 *
 * @author Rubens Gomes <rubens.s.gomes@gmail.com>
 */
class Contact
{

    /** @var array $validationRules The rules for validating input data */
    private static $validationRules = [
        'name' => 'required|max:100','email' => 'required|email:rfc,dns','msg' => 'required|max:1000',];

    /** @var string $name The contact name */
    private $name;

    /** @var string $email The contact email address */
    private $email;

    /** @var string $msg The contact message */
    private $msg;

    /**
     * Default constructor.
     *
     * @param string $name
     *            The non-blank contact name.
     * @param string $email
     *            The non-blank contact email.
     * @param string $msg
     *            The non-blank contact message.
     * @throws \InvalidArgumentException if any of the arguments is not valid.
     */
    public function __construct($name,$email,$msg)
    {
        $data = [
            'name' => $name,'email' => $email,'msg' => $msg
        ];

        $validator = Validator::make($data,self::$validationRules);

        if($validator->fails()) {
            Log::error("name or email invalid",$data);
            throw new \InvalidArgumentException("Invalid argument: " . $validator->errors());
        }

        $this->name = $name;
        $this->email = $email;
        $this->msg = $msg;
    }

    public function getEmail() : string
    {
        return $this->email;
    }

    public function getMessage() : string
    {
        return $this->msg;
    }

    public function getName() : string
    {
        return $this->name;
    }

}
/******************************************************************************/

// EOF

ContactRequestMailable.php:

<?php
namespace App\Mail;

use App\Model\Contact;
use Illuminate\Mail\Mailable;

/**
 * Mailable used to send email of message in contact form.
 *
 * @author Rubens Gomes
 */
class ContactRequestMailable extends Mailable
{

    private const SUBJECT = "Rubens Gomes - Contact";

    /** @var Contact The contact instance. */
    private $contact;

    /**
     * Default constructor
     *
     * @param Contact $contact
     *            the non-null and valid contact instance.
     * @throws \InvalidArgumentException if any of the arguments is not valid.
     */
    public function __construct($contact = null)
    {
        if (is_null($contact)) {
            throw new \InvalidArgumentException("contact cannot be null.");
        }

        $this->contact = $contact;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->subject(self::SUBJECT)
            ->locale('en')
            ->from(config('mail.from.address'),config('mail.from.name'))
            ->to(config('mail.from.address'),config('mail.from.name'))
            ->view('emails.contact-request')
            ->with([
            'name' => $this->contact->getName(),'email' => $this->contact->getEmail(),'msg' => $this->contact->getMessage()
        ]);
    }
}
/******************************************************************************/

// EOF```