How to prevent Laravel event() from firing during unit tests - php

I have a class that uses Laravel's global event() function to fire off a single event when a model is changed. The only way I have been able to prevent this event from firing during unit tests is to actually namespace and declare a new event() function in the test itself and make it do nothing. It works, but it doesn't seem like a pretty solution to me. I looked into the Laravel docs and I can see some people have used Event::fake() inside the test successfully, but when I try to do it I get:
BadMethodCallException: Method Mockery_0_Illuminate_Contracts_Events_Dispatcher::until() does not exist on this mock object
I'm on Laravel 5.4. Is there a cleaner way to prevent this event from firing during a test? I really dislike the idea of declaring an empty namespaced event() function.
EDIT:
The class I am testing is a UserDomain class. In one part of the logic it invokes Laravel's global event() method:
event(new RoleChanged($this->user));
To suppress this from firing in a test I have tried Event::fake() and I have also tried using the trait WithoutEvents and its withoutEvents() method. Neither work, and the same error I mentioned above occurs both times.

You need to use Illuminate\Foundation\Testing\WithoutEvents trait in your test.
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithoutEvents;
use Illuminate\Foundation\Testing\RefreshDatabase;
class FooTest extends TestCase
{
use WithoutEvents;
}

I read some more docs on mocking in Laravel unit testing and I discovered that all I had to do is this at the beginning of the test:
$this->expectsEvents(RoleChanged::class);
This tells framework to acknowledge this event occurred but to not actually fire it off. Thanks everybody for your help. It led me to a workable solution.

Use Illuminate\Foundation\Testing\WithoutEvents trait, and place $this->withoutEvents() before any test.
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithoutEvents;
class UsersTest extends TestCase
{
use WithoutEvents;
/**
* #test
*/
public function email_is_required()
{
$this->withoutEvents();
// assertions
}
}

Related

How to make Mockery overload option work properly on Laravel 5

I am trying to use overload option of Mockery library on Laravel 5.
My current environment:
Laravel 5
Mockery 1.0
PHPUnit 7.5
I wrote this test case:
namespace Tests\Unit;
use Mockery;
use Tests\TestCase;
/**
* #runTestsInSeparateProcesses
* #preserveGlobalState disabled
*/
class RenewSignatureTest extends TestCase
{
public function testHandle()
{
$mock = Mockery::mock('overload:App\FooClass');
$mock->shouldReceive('callBar')
->times(2);
}
}
According to documentation, this test should fail, but does not matter what I do, the test never fails! It always result in:
Time: 304 ms, Memory: 19.53 MB
OK (1 test, 1 assertion)
If I remove the overload: option, the test fails. So I assume that I'm not using the library's methods as expected.
The new test:
namespace Tests\Unit;
use Mockery;
use Tests\TestCase;
/**
* #runTestsInSeparateProcesses
* #preserveGlobalState disabled
*/
class RenewSignatureTest extends TestCase
{
public function testHandle()
{
$mock = Mockery::mock('App\FooClass');
$mock->shouldReceive('callBar')
->times(2);
}
}
The result:
Mockery\Exception\InvalidCountException: Method callBar(<Any Arguments>) from Mockery_0__App_FooClass should be called exactly 2 times but called 0 times.
Am I doing anything wrong? Does anyone know how to use this option properly?
Reading the page, I think this is the error you are looking for:
When Mockery overloads a class, because of how PHP works with files,
that overloaded class file must not be included otherwise Mockery will
throw a “class already exists” exception. This is where autoloading
kicks in and makes our job a lot easier.
The error that you're looking for will be caused if you remove these 2 lines from your test, which are added to the code in the second code sample, and in the third sample on the manual page:
* #runTestsInSeparateProcesses
* #preserveGlobalState disabled
This means your code wont take advantage of the psr4 autoloader or any autoloader that is in place, and will create a new autoloader for you, at the expense of speed, since it wont be using your dumped classmap and has to build it up from scratch.
If you take the two lines above out, you will get the expected error, as it will try to overload your class with a class of the same name. That class will already be autoloaded, so you get a fatal error.
So if you want to block calls to callBar, and return void, that is what your code will be doing, which is correct.
Removing overload will mean your mock is no longer effective, as you will have to pass it through a constructor to get it to work.
Update:
With your update, I can conclude that your code must be running the mocked callBar method twice (not the actual callBar method), with your mock of fooBar class using overload. When the mocked method gets called, nothing inside the real callBar method actually happens, it just registers that it was called. If you're expecting it once for example, write shouldReceive(1) and then fix the code that your test runs.
When you remove the overload, the global injection doesnt take place, so your mock never gets injected. However, your mock callBar method on the mock class is still expecting to be ran twice, so you get the error. You will need to remove the 2 mock code lines completely from your test.
Keep the # statements in, as it will help prevent the psr4 error outlined above.
This is not the way to test, you should never use overload: option... Laravel helps you with a lot of things, don't create the wheel again or try to...
Why would you need to use #runTestsInSeparateProcesses and #preserveGlobalState disabled ?
Let's say your test is like this:
namespace Tests\Unit;
use App\FooClass;
use App\Service\ServiceForTesting;
use Mockery;
use Tests\TestCase;
class RenewSignatureTest extends TestCase
{
public function testHandle()
{
$mock = Mockery::mock(FooClass::class);
$mock->shouldReceive('callBar')
->times(2);
$myClass = app(ServiceForTesting::class);
$myClass->run($mock);
$myClass->run($mock);
}
}
You should have a code like this:
namespace App;
class FooClass
{
public function callBar()
{
return 'Works !!!';
}
}
Now, let's say you have a standalone class (no controller or similar, you are in a unit test), you should have something like this:
namespace App\Service;
use App\FooClass;
class ServiceForTesting
{
public function run(FooClass $class)
{
return $class->callBar();
}
}

How to prevent a false-warning in PhpStorm when the return-type is deduced incorrectly?

Consider the following code:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
/**
* Class MyModel
* #package App\Models
* #mixin Builder
*/
class MyModel extends Model
{
public static function getGreens(): Builder
{
return (new self())->where('color', '=', 'green');
}
}
On the return statement, the PhpStorm (2020.3) complains that:
Return value is expected to be '\Illuminate\Database\Eloquent\Builder', 'MyModel' returned
And suggest to:
Change return type from '\Illuminate\Database\Eloquent\Builder' to 'MyModel'
which is weirdly incorrect (the where method does return an instance of \Illuminate\Database\Eloquent\Builder, while the IDE deduces the return type as being of MyModel type). By removing the return type, the IDE issues another warning:
Missing function's return type declaration
The code works without any problems, but the IDE shouldn't report any false warnings! How should I avoid these warnings in PhpStorm?
It's a result of not following the "best practices". The class-hierarchy of MyModel does not provide a method of where; in other words, such a method does not exist in the class-hierarchy. But! The parent class of Model does provide a magic method of __call() which gets triggered when an inaccessible method in the object context is invoked (in your case, the method of where). It essentially forwards the "call" to a new instance of \Illuminate\Database\Eloquent\Builder, that has the implementation of the requested method (it's acquired from invoking the method of newQuery()). This mechanism is not only IDE-unfriendly, but also slower.
Thus; drop the #mixin tag, and instead of utilizing the "magic methods", use "native access":
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class MyModel extends Model
{
public static function getGreens(): Builder
{
return (new self())->newQuery()->where('color', '=', 'green');
// ^^^^^^^^^^^^
}
}
As I understand (based on how Laravel works) it's because of #mixin line.
#mixin tag works similar to how PHP's native trait works. So if you have a method in a trait that returns $this / self and then use that trait in a class, then return of that method ($this/self) points to the class where it is used.
Now, Builder::where() method also returns $this or self ... but it's not actually a trait but Laravel magically makes that where() method available in this class.
And here comes the problem: that #return $this actually points to the Builder class but when used "as a trait" (because of #mixin) it gets resolved to the current MyModel class by the IDE.
You either use #mixin and live with ignoring the issue (you can use error suppression via Alt + Enter quick fix menu -- it will add a comment for IDE to tell to ignore that specific issue here) .. or remove #mixin and declare those methods differently.
AFAIK Laravel helper package should add all such Builder methods to the Model class via #method PHPDoc lines (look into that for details, go through past issues there to see how and why it does that etc, e.g. #541).
Another suggestion: try Laravel Idea plugin -- it's a PAID plugin but it makes working with Laravel code much easier and AFAIK it should cover such basic stuff.
For reference:
https://youtrack.jetbrains.com/issue/WI-1730 -- original request for documenting/supporting mixins
Outstanding #mixin tickets that can be related here (at least partially):
https://youtrack.jetbrains.com/issue/WI-34809
https://youtrack.jetbrains.com/issue/WI-49567
or try your own search, e.g. https://youtrack.jetbrains.com/issues/WI?q=mixin%20laravel

Use of service providers within controllers in Laravel 5.2

As for the title I've googled about two hours searching for a efficient answer and read repeatedly the official documentation, but without any step further, considering I'm relatively new to the framework. The doubt arise while searching for a correct way to share some code between controllers and i stumbled in service providers, so:
I've created say a MyCustomServiceProvider;
I've added it to the providers and aliases arrays within the app.php file;
finally I've created a custom helpers class and registered it like:
class MyCustomServiceProvider extends ServiceProvider
{
public function boot()
{
//
}
public function register()
{
$this->app->bind('App\Helpers\Commander', function(){
return new Commander();
});
}
}
So far, however, if I use that custom class within a controller I necessarily need to add the path to it through the use statement:
use App\Helpers\Commander;
otherwise I get a nice class not found exception and obviously my controller does not his job.
I suspect there's something which escapes to me on service providers! :-)
So far, however, if I use that custom class within a controller I
necessarily need to add the path to it through the use statement:
`use App\Helpers\Commander;`
otherwise I get a nice class not found
exception and obviously my controller does not his job.
Yes, that's how it works. If you don't want to use the full name, you can use a Facade instead.
Create the Facade class like this:
class Commander extends Facade
{
protected static function getFacadeAccessor() { return 'commander'; }
}
register the service:
$this->app->singleton('commander', function ($app) {
return new Commander();
});
add the alias to your config/app.php:
'aliases' => [
//...
'Commander' => Path\To\Facades\Commander::class,
//...
],
and use it like a Facade:
\Commander::doStuff();
On why your code still works, even when you remove the bind:
When you type-hint a parameter to a function, and Laravel does not know about the type you want (through binding), Laravel will do its best to create that class for you, if it is possible. So even though you didn't bind the class, Laravel will happily create a instance of that class for you. Where you actually need the binding is when you use interfaces. Usually, you'd not type-hint specific classes but a interface. But Laravel can not create a instance of an interface and pass it to you, so Laravel needs to know how it can construct a class which implements the interface you need. In this case, you'd bind the class (or the closure which creates the class) to the interface.

Class not found, Laravel Observer

I'm in the process of switching from Laravel 4.2 to Laravel 5, not sure if that is relevant, but I am getting an error:
"Class 'library\observers\UserObserver' not found"
and I have no Idea what the problem is, as far as I can see ( through my frustration mind you ) is that everything is in its right place, name spaces, folders, class names etc.. and I've ran the artisan dump autoload command twice now. the class is an observer which modifies user input on save. here is my code:
UserObserverServiceProvider.php:
<?php namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use library\observers\UserObserver;
use App\Models\User;
class UserObserverServiceProvider extends ServiceProvider
{
public function boot()
{
User::observe( new UserObserver );
}
public function register(){}
}
UserObserver.php:
<?php namespace library\observers;
use library\Facades\Geo;
use Geocode;
use State;
use City;
class UserObserver{ code for user observer }
app.php configuration for service provider:
'App\providers\UserObserverServiceProvider',
All of these things were working together before the switch, what am I missing?
I left the App out of the namespace and path for use, works now, thanks!

Mockery shouldReceive()->once() doesn't seem to work

I'm trying to get Mockery to assert that a given method is called at least once.
My test class is:
use \Mockery as m;
class MyTest extends \PHPUnit_Framework_TestCase
{
public function testSetUriIsCalled()
{
$uri = 'http://localhost';
$httpClient = m::mock('Zend\Http\Client');
$httpClient->shouldReceive('setUri')->with($uri)->atLeast()->once();
}
}
As you can see, there's one test that (hopefully) creates an expectation that setUri will be called. Since there isn't any other code involved, I can't imagine that it could be called and yet my test passes. Can anyone explain why?
You need to call Mockery:close() to run verifications for your expectations. It also handles the cleanup of the mockery container for the next testcase.
public function tearDown()
{
parent::tearDown();
m::close();
}
To avoid having to call the close method in every test class, you can just add the TestListener to your phpunit config like so:
<listeners>
<listener class="\Mockery\Adapter\Phpunit\TestListener"></listener>
</listeners>
This approach is explained in the docs.
One thing to note from the linked docs is:
Make sure Composer’s or Mockery’s autoloader is present in the bootstrap file or you will need to also define a “file” attribute pointing to the file of the above TestListener class.
Just a sidenote: If you use Laravel: the make:test --unit generates a test class that extends the original PhpUnit Testcase class and not the included Tests\Testcase, which loads the laravel app and runs the Mockery::close(). It is also the reason why in some cases your tests fail if you use Laravel specific code (like Cache, DB or Storage) in the units you're testing.
so if you need to test units with Laravel specific code, just swap out the 'extends Testcase' and there is no need to call Mockery::close() manually

Categories