We have the following middleware. We know the middleware works since testing the application in browser is working. Sadly when writing HTTP test case, blade is saying that the variable defined by the middleware is not there.
<?php
namespace App\Http\Middleware;
use App\Repository\UserContract;
use Closure;
use Illuminate\Contracts\View\Factory as ViewFactory;
use Illuminate\Support\Facades\View;
class Authenticate
{
private $userRepository;
private $view;
public function __construct(UserContract $userRepository, ViewFactory $view)
{
$this->userRepository = $userRepository;
$this->view = $view;
}
public function handle($request, Closure $next)
{
$userId = null;
$username = null;
if ($request->hasCookie('auth')) {
$secret = $request->cookie('auth');
$userId = $this->userRepository->getUserIdBySecret($secret);
$username = $this->userRepository->getUsername($userId);
}
$this->view->share('username', $username);
$request['_user_id'] = $userId;
$request['_username'] = $username;
return $next($request);
}
}
We doing a PHPUnit test as following:
/**
* @test
*/
public function it_shows_logged_in_username()
{
$app = $this->createApplication();
$encrypter = $app->get(Encrypter::class);
$userRepository = $app->make(UserContract::class);
$userRepository->addUser('jane', 'secret');
$secret = $encrypter->encrypt('secret', false);
$response = $this->call('GET', '/', [], ['auth' => $secret], [], [], null);
$response->assertSeeText('jane');
}
error
ErrorException {#980
#message: "Undefined variable: username"
#code: 0
#file: "./storage/framework/views/db4b9232a3b0957f912084f26d9041e8a510bd6c.php"
#line: 3
#severity: E_NOTICE
trace: {
./storage/framework/views/db4b9232a3b0957f912084f26d9041e8a510bd6c.php:3 {
› <?php dump($comments); ?>
› <?php dump($username); ?>
Any advice would be appreciated?
EDITS
I have to add that using the View
facade and called the share
methods on it make it works but I hit the same issue with the errors
variable that should be set by the \Illuminate\View\Middleware\ShareErrorsFromSession
stock laravel middleware.
Other remark, my middleware is called Authenticate
but it has nothing to do with the normal password based authentication of Laravel.
Here the UserContract
:
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Repository\Exception\UserAlreadyRegisteredException;
interface UserContract
{
public function isRegistered(string $username): bool;
public function getUserID(string $username): int;
public function getUserIdBySecret(string $secret): int;
/**
* @param int $userId
*
* @return string
* @throws
*/
public function getUsername(int $userId): string;
/**
* @param int $userId
*
* @return string
* @throws AuthSecretNotSetException
*/
public function getUserAuthSecret(int $userId): string;
public function getNextId(): int;
/**
* @param string $username
* @param string $authSecret
*
* @return int
* @throws UserAlreadyRegisteredException
*/
public function addUser(string $username, string $authSecret): int;
public function fetchUpdatedBetween(?\DateTimeInterface $start, ?\DateTimeInterface $end): array;
public function markAsSynced(int $userId): void;
public function isSynced(int $userId): bool;
public function delete(int $userId): void;
public function markAsDeleted(int $userId): void;
public function fetchDeletedIds(): array;
public function removeDeletedFlag(int $userId): void;
public function fetchStalledIds(\DateTimeInterface $dateTime): array;
public function purge(int $userId): void;
/**
* @param UserFetcherContract $fetcher the closure would be passed userId and it should return data or null
*/
public function setFallbackFetcher(UserFetcherContract $fetcher): void;
}
I finally took the time to do a deep dive on this one.
It turns out the issue is happening because I am creating multiple instance of the booted app and somehow it mess with the instance of the view factory registered in the the application.
In the test case removing this line make it works:
/**
* @test
*/
public function it_shows_logged_in_username()
{
// THIS IS WRONG
// $app = $this->createApplication();
$app = $this->app; // Use application instantiated in setUp method of test case
$encrypter = $app->get(Encrypter::class);
$userRepository = $app->make(UserContract::class);
$userRepository->addUser('jane', 'secret');
$secret = $encrypter->encrypt('secret', false);
$response = $this->call('GET', '/', [], ['auth' => $secret], [], [], null);
$response->assertSeeText('jane');
}
I am not 100% sure why booting multiple instance of the app would create issues but my gut feeling is: some shared data somewhere...
For any of you interested here the commit