Laravel Unit Test with Session Facade fails on HHVM - php

When I use the Session facade within a Laravel Test, I get failures within HHVM systems. I'm using Travis.CI to test a range of systems from PHP 5.4 right up to PHP 7, including HHVM. Throughout my range of tests, wherever Sessions are used, the tests fail, but only on HHVM systems; all others pass.
Here's an example test:
/**
* Tests that, provided all the data lines up, we can add points to a child
*/
public function testPointAdditionWithCorrectChild()
{
Session::start();
// Set up User
$user = factory(User::class)->create();
$this->be($user);
// Create Child
$child = factory(Child::class)->create([
'user_id' => $user->id
]);
// Set up Request
$pointData = [
'_token' => csrf_token(),
'child' => $child->id,
'points' => mt_rand(1, 10)
];
$this->post('/point/increment', $pointData);
// Assertion
$this->seeInDatabase('points', [
'child_id' => $pointData['child'],
'point_difference' => $pointData['points']
]);
}
The purpose of the above test is to ensure that my middleware functions when authenticating a child against a user with the correct credentials - restricting the ability to add/remove points so that only the user who created the child is allowed to do this.
Laravel comes with a $this->withoutMiddleware(); flag which allows me to turn off middleware for a particular test. However, as I'm testing my middleware in this instane, I cannot use it, meaning I also have to satisfy the all the other middlewares assigned to the controller as well, such as the VerifyCsrfToken middleware. To do this, I have to run Session::start() otherwise I cannot take advantage of the csrf_token() function. Therein lies my problem. I suppose it's an integration test, more than a unit test.
Is there something specific about the way that HHVM deals with sessions that is causing these tests to fail?

Related

How to reset Laravel AuthManager/guards in between API calls in tests?

I'm writing a Feature test for an API and I want to test custom auth logic.
I know that when I call the login API endpoint, Laravel caches the fact that the user is logged in so the next API call in the test would consider the user already authenticated...
So for one test, how do I disable this Laravel magic of auth caching so I can manually provide the Bearer auth token to check if my custom authentication logic works?
I'm thinking something along the lines of clearing the guards in AuthManager, or clearing AuthManager entirely so Laravel would be force to reinitialize it. But I'm having no luck in figuring out how to do that in a way that works.
Here's some pseudo-example code:
public function testLogin()
{
$responseData = $this->post('/login', ['email' => $this->user->email, 'password' => 'password'])->json();
$this->get('/endpoint-requiring-auth')->assertOk();
//
// $this->logicToResetAuth() ?
//
$this->get('/endpoint-requiring-auth')->assertUnauthorized();
// I want to manually have to pass the token to get it to work now.
$this->get('/endpoint-requiring-auth', ['Authorization' => "Bearer $responseData[access_token]"])
->assertOk();
}
Also the unknown reset logic should affect multiple guards.
Oh yeah, I'm writing custom logic around the JWT-Auth library.
For Laravel 8.x (and maybe newer)
auth()->forgetGuards();
But for JWT you may need to additionally do:
app('tymon.jwt')->unsetToken();
Or
app()->instance('tymon.jwt', null);
For Laravel 7.x (and maybe older)
As ->forgetGuards() is not invented yet in this version, and guards-array is protected, do something like:
/** #var \Illuminate\Auth\AuthManager $auth */
$auth = app('auth');
$mirror = new \ReflectionObject( $auth );
$prop = $mirror->getProperty( 'guards' );
$prop->setAccessible(true);
$prop->setValue($auth, []);
$prop->setAccessible(false); // Back to protected.
But for JWT, do same as above Laravel 8 section.
For jwt-auth, you can do this to clear the auth user:
auth()->forgetGuards();
app()->instance('tymon.jwt', null);
The first line, discards the existing cached guards. And the second, ensures when the guard is re-created, it wont reuse the same underlying jwt instance.
Good question. I was also struggling with this too. Yesterday I performed a deep dive on this subject. Turned out that TokenGuard has en implementation problem as the authenticated user is not refreshed after a new request is loaded. In order to fix this the following is needed:
Extends TokenGuard class into your own class and overload method setRequest. Add '$this->user = null;' before calling the parent implementation by 'return parent::setRequest($request);'
Use Auth::extend to be able to use your custom TokenGuard
After that, every new request resets the authenticated user automatically
See also https://code.tutsplus.com/tutorials/how-to-create-a-custom-authentication-guard-in-laravel--cms-29667

Route to URL helper default parameters

I am using the code found here https://laracasts.com/discuss/channels/laravel/how-to-prefixing-in-routes-for-localization to prefix my routes with a locale.
Route::prefix('{lang?}')->middleware('locale')->group(function() {
Route::get('/', function () {
return view('index');
})->name('index');
});
This locale is optional, but then my question is how am I supposed to reinject the locale when calling the route helper.
route('index');
to generate: /it/ or /, or another, depending on the current locale.
If have tried this with no success :
Route::resourceParameters(['lang' => 'en']);
Actually, I'm a bit unsure about what the question is. I took it as a way to generate URL without setting its dynamic parameters using route helper based on #rahulsm's answer.
So, I just figured out that you can set default parameters to UrlGenerator class. By listening on container url and request rebind event, it's possible to set default parameters there.
Inside AppServiceProvider boot
$this->app->rebinding('url', function ($url, $app) {
$url->defaults(['lang' => $app->getLocale()]);
});
$this->app->rebinding('request', function ($app) {
$app['url']->defaults(['lang' => $app->getLocale()]);
});
And then tried to call it,
route('index') // http://test.dev/en (based on config)
route('index', ['lang' => 'what']) //http://test.dev/what
This was tested only on Laravel 5.5, but I'm sure would also working on Laravel 5.4.
To explain a bit more about rebinding method available Laravel container[1], firstly it's good to know how is Laravel request lifecycle works. In a one line simplified words, it should be the same as how an application in general works, which is: receive a request, do the logic, return a response.
At the second part, appear stage commonly said as bootstrapping, which one of its logic is try to store (register) classes (services) that mostly are important for the application[2] to work. Into a container. Thus, intended to be shared or even just for easily to be called, whether for the high-end developers or the framework itself.
Once registered, it will then be booted. This booting process has several actions to call[3]. One of it that suit this case is firing (local) events. Once a service has been resolved, it will try to call all registered rebound functions (listeners) at this stage. I can only assume this purpose is to make the application can be "easily mutate" the currently-resolving instance (service). Thus, defining rebinding method to listen to recalled event is the way to go.
Since Laravel allows to re-resolve (re-instantiate) a service, which means our previous stored value in the class is lost[4], waiting it to be resolved (which then the listener called) is much make sense, right?
Back to rebinding snippet above, I used to listen to both url and request rebound services because url is request's dependent. It waits for request service to be fully resolved and then call setRequest[5] method which flushes the needed instance which is Illuminate\Routing\RouteUrlGenerator that holds default parameter.
And as the name implies, defaults, used to set the default named parameters used by the URL generator[6].
cit
[1] Container here refer to both Illuminate\Foundation\Application also Illuminate\Container\Container
[2] configure error handling, configure logging, detect the application environment
[3] resolving type-hinted dependencies, storing/caching stuffs to buffer, etc
[4] Unless retained like stored in class' static property
[5] Illuminate\Routing\UrlGenerator#setRequest
[6] Illuminate\Routing\UrlGenerator, either calling URL::route('route.name'), url('your-url'), app('url')->route('route.name'), or route('route.name'), they refer to the same class
This should should be,
route('index', ['lang' => 'fr']);
And change route helper with
/**
* Generate a URL to a named route.
*
* #param string $name
* #param array $parameters
* #param bool $absolute
* #param \Illuminate\Routing\Route $route
* #return string
*/
function route($name, $parameters = [], $absolute = true, $route = null)
{
if (!isset($parameters['lang'])) $parameters['lang'] = App::getLocale();
return app('url')->route($name, $parameters, $absolute, $route);
}
Refer link for more details.

Writing functional tests that check if Laravel jobs were dispatched with correct parameters?

I'm trying to write a functional test to check if one of the methods of our REST api correctly dispatches a job.
I know Laravel includes the expectsJobs method. But that only checks if the job was dispatched. I also need to check if the params it was instantiated with are correct.
That way, I could rely on this functional test + another unit test that checks if the job itself runs fine when instantiated with the correct parameters. Otherwise, I only know that:
the rest api call dispatches the job
the job works fine when instantiated with the correct params
But I don't know if the job gets instantiated with the correct params by the rest api call.
For clarity's sake, here is a bit of pseudo code:
Functional test for REST api call:
$this->expectsJobs(\App\Jobs\UpdatePricesJob);
$this->json("POST", "/api/resource/{$someresource->id}", [
"update_prices" => 1,
"prices" => [
/** the prices list **/
]
])->seeJson(["success" => true]);
/** The test succeeds if an UpdatePricesJob is dispatched **/
Unit test for UpdatePricesJob:
$someresource = Resource::find($someResourceId);
$newPrices = [ /** properly-formatted prices **/ ]
$this->dispatch(new UpdatePricesJob($someresource, $newPrices));
$this->assertEquals($someresource->prices, $newPrices);
/** The test succeeds if running UpdatePricesJob properly updates the resource prices to the ones specified in the job params **/
As you can see, there is no way to know if the REST api call instantiates UpdatePricesJob with some properly formatted prices.
Thanks in advance!

Laravel's call method bypasses filters

I am trying to run Unit Tests for Laravel using PHPUnit.
My calls to the functions seem to get to the controller, however, they seem to bypass my filters. The filters are used in order to do user validation, session tokens and so not. The call seems to go directly to the controller and forgets about the filters.
public function testBasicExample(){
$response = $this->call('GET', 'URL_Here', array('param_1' => 'value_1', 'param_2' => 'value_2' ));
echo $response->getContent(); /**This should not be returning true if values are wrong and it is **/
$this->assertTrue($this->client->getResponse()->isOk());
}
Any ideas?
According to the Laravel documentation, the route filters are disabled when in the testing environment. You can use the following call to enable them:
Route::enableFilters();
You can read more about this here. The Note block at the end of that chapter answers your question.

Yii Unit-Testing with a Logged in Web User

Im writing some unit tests and bear with me I am still very new to unit testing.
The issue I am having is a lot of my saves invoke a behaviour that requires
the users id from Yii::app()->user->id.
However when I run the UnitTest I get problems as the user isn't logged in.
Is there anyway I can either ignore the behaviour by a flag (e.g. if ($isInTestingMode)) or log the user in within the testing class?
I would probably build a user object that you use in your tests. And then in the appropriate tests (as part of the setup method, like ernie describes in his comment), swap in the testing user object.
The test user object would then have a method that works like this:
public function getId() {
return 12;
}
public function getIsGuest() {
return false;
}
The above is what they call a 'Fake' object.
In your setup method you'd use the following lines:
Yii::app()->configure(array(
'components' => array(
'user' => array(
'class' => 'path.to.FakeUser',
)
)
));
You can also add that to your test config file if you want that to be the default user (and then swap in the normal CWebUser/WebUser model in tests that need to have a non-logged in user.
Or you could have a flag you set for your FakeUser (isLoggedIn = true/false) in each unit test. I'd probably go with this option myself ...
Tests like these are not unit tests. Unit tests are for testing individual functions in a class. A "logged in user" covers a large swath of code (logging in, session management, navigating to a specific page, etc.). I believe you're looking for functional testing. You can learn more about that here: http://www.yiiframework.com/doc/guide/1.1/en/test.functional

Categories