Been looking all over the internet but don't seem to find an answer to my problem. I've been diving into testing controllers in Laravel using PHPUnit and Mockery. However, I don't seem to get my Eloquent based models mocked correctly. I did manage to mock my Auth::user() the same way, although this is not used in the test below.
Function in AddressController that needs to be tested:
public function edit($id)
{
$user = Auth::user();
$company = Company::where('kvk', $user->kvk)->first();
$address = Address::whereId($id)->first();
if(is_null($address)) {
return abort(404);
}
return view('pages.address.update')
->with(compact('address'));
}
ControllerTest containing setUp and mock method
abstract class ControllerTest extends TestCase
{
/**
* #var \App\Http\Controllers\Controller
*/
protected $_controller;
public function setUp(){
parent::setUp();
$this->createApplication();
}
public function tearDown()
{
parent::tearDown();
Mockery::close();
}
protected function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
}
AddressControllerTest extending ControllerTest
class AddressControllerTest extends ControllerTest
{
/**
* #var \App\Models\Address
*/
private $_address;
/**
* #var \App\Http\Controllers\AddressController
*/
protected $_controller;
public function setUp(){
parent::setUp();
$this->_controller = new AddressController();
$this->_address = factory(Address::class)->make();
}
public function testEdit404(){
$companyMock = $this->mock(Company::class);
$companyMock
->shouldReceive('where')
->with('kvk', Mockery::any())
->once();
->andReturn(factory(Company::class)->make([
'address_id' => $this->_address->id
]));
$addressMock = $this->mock(Address::class);
$addressMock
->shouldReceive('whereId')
->with($this->_address->id)
->once();
->andReturn(null);
//First try to go to route with non existing address
$this->action('GET', 'AddressController#edit', ['id' => $this->_address->id]);
$this->assertResponseStatus(404);
}
}
The error it keeps throwing is:
1) AddressControllerTest::testEdit404
Mockery\Exception\InvalidCountException: Method where("kvk", object(Mockery\Matcher\Any)) from Mockery_1_Genta_Models_Company should be called exactly 1 times but called 0 times.
Perhaps anyone has an idea?
Okay, after finding multiple posts by Jeffrey Way (the guy behind Laracasts) recommending people to stop mocking Eloquent objects and instead use in memory databases I've tried that approach. I thought this would perhaps be very usable for other users having the same problems in the future, so I'll explain below.
Right now I've edited the my 'config/database.php' file to support in-memory database option using sqlite:
'sqlite' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
Next on top of the file you'll see the following configuration:
'default' => env('DB_CONNECTION', 'mysql'),
This can stay the same, it means that Laravel will check your .env variables to find a 'DB_CONNECTION' or else use mysql as default. This is probably what you'd like to do when running your application as usual. However with testing you would like to override this configuration and set the database config to 'sqlite' temporarily. This can be done by adding the 'DB_CONNECTION' variable to your .env file:
DB_CONNECTION=mysql
Finally in your phpunit.xml, the configuration file used by Laravel to instantiatie the unit tests, you have to tell that when testing this variable should be set to 'sqlite':
<env name="DB_CONNECTION" value="sqlite"/>
Now you are done and Laravel will start up an invisible in-memory database everytime you are about to go testing. Don't forget to add the following line to tests that need the database.
use \Illuminate\Foundation\Testing\DatabaseMigrations;
It will tell Laravel run your database migrations before starting the tests, so you can use the tables like you normally would.
This way it works perfectly for me! Hope you guys can use it.
Related
I am trying to listen to model events using laravel observers .The problem is when i submit my form (update or creating new records), nothing happened at all .Do i miss something ?
app.php
'providers' => [
...
App\Providers\CasesManagerServiceProvider::class,
]
CasesManagerServiceProvider.php
class CasesManagerServiceProvider extends ServiceProvider
{
public function boot( )
{
Cases::observe(CasesObserver::class);
}
public function register()
{
}
}
CasesObserver.php
class CasesObserver
{
private $cases;
public function __construct(Cases $cases){
$this->cases = $cases;
}
public function creating(Cases $case)
{
dd('creating');
}
public function saved(Cases $case)
{
dd('saved');
}
public function updating($case)
{
dd('updating');
}
public function updated($case)
{
dd('updated');
}
}
Cases.php
class Cases extends Model
{
const UPDATED_AT = 'modified_at';
protected $dispatchesEvents = [
'updating' => CasesObserver::class,
'updated' => CasesObserver::class,
'creating' => CasesObserver::class,
'saved' => CasesObserver::class,
];
}
for me, the problem was registering observer in the register() method!
so when I put it in the boot() method every thing worked well! the reason is the order of running methods in service providers which are mentioned hear
hope be useful
Ok i have found my answer . All the problem was when I added
use app\Observers\CasesObserver; in CasesManagerServiceProvider.php instead of use App\Observers\CasesObserver; .
Yes the Camel case of App was the problem, so i changed to App and all things are working fine now.
It seems to be a misuse of Composer and Laravel themselves.
You should inform them that you have added some files and configurations:
To autoload the files:
composer dump
To reconfigure the cache:
php artisan config:cache
Hope this help you too!
You do not need to use $dispatchesEvents in your case. You should try to remove $dispatchesEvents from model, and remove __constructor() from CasesObserver.
The reason is that you have to add a HasEvents trait to your model
<?php
use Illuminate\Database\Eloquent\Concerns\HasEvents;
class MyModel extends Model
{
use HasEvents;
//your code goes here
}
Not possible according to the documentation. When issuing a mass update or delete query via Eloquent.
I'm having some trouble getting my unit test to work. I'm testing a controller that uses a service that is created by a factory. What I want to achieve is to replace a factory with a mocked service so I can perform tests without using an active database connection.
The setup
In my service manager's configuration file I point to a factory.
The factory requires an active database connection that I don't want to use during my unit test.
Namespace MyModule;
return [
'factories' => [
MyService::class => Factory\Service\MyServiceFactory::class,
],
];
Note: I have changed class names and simplified configuration for illustration purposes.
The service uses a mapper that I won't be going into now because that is not relevant to the situation. The mappers are tested in their own testcases. The service itself has it's own testcase as well but needs to be present for the controller's actions to work.
The controller action simply receives information from the service.
Namespace MyModule\Controller;
use MyModule\Service\MyService;
use Zend\Mvc\Controller\AbstractActionController;
class MyController extends AbstractActionController
{
/**
* #var MyService
*/
private $service;
/**
* #param MyService $service
*/
public function __construct(MyService $service)
{
$this->service = $service;
}
/**
* Provides information to the user
*/
public function infoAction()
{
return [
'result' => $this->service->findAll(),
];
}
}
Note: Again, I have changed class names and simplified the example for illustration purposes.
What I've tried
In my testcase I've tried to override the desired factory like this:
/**
* #return \Prophecy\Prophecy\ObjectProphecy|MyService
*/
private function mockService()
{
$service = $this->prophesize(MyService::class);
$service->findAll()->willReturn(['foo', 'bar']);
return $service;
}
/**
* #param \Zend\ServiceManager\ServiceManager $services
*/
private function configureServiceManager(ServiceManager $services)
{
$services->setAllowOverride(true);
$services->setService(MyService::class, $this->mockService()->reveal());
$services->setAllowOverride(false);
}
At first sight this looks great, but it doesn't work. It just seems to append the service to the service manager's list of services, not overriding the factory.
Changing $services->setService to $services->setFactory requires me to build another factory. What I could do is create a factory that injects a mock-mapper into the service but that feels wrong. I'm testing the controller, not the service or mapper so I am trying to avoid complex solutions like that to keep my test cases simple and clear.
Are there any options regarding my situation? Is it possible to override a factory with a service in the service manager or am I looking at it wrong?
I think you need a separate config file for unit testing.
phpunit.xml
<?xml version="1.0"?>
<phpunit bootstrap="./Bootstrap.php">
Bootstrap.php
require 'vendor/autoload.php';
$configuration = include 'config/phpunit.config.php';
Zend\Mvc\Application::init ($configuration);
config/phpunit.config.php is a config file created for unit testing only:
config/phpunit.config.php
$configuration = include (__DIR__ . '/application.config.php');
$configuration ['module_listener_options'] ['config_glob_paths'] [] = 'config/phpunit/{,*.}local.php';
config/phpunit/yourfile.local.php
return [
'service_manager' => array (
'factories' => [
MyService::class => ...
]
)
];
In config/phpunit/yourfile.local.php you can let MyService::class be whatever you want, even a closure.
There is no need to build new factories for this. Just use a simple closure instead:
/**
* #param \Zend\ServiceManager\ServiceManager $services
*/
private function configureServiceManager(ServiceManager $services)
{
$services->setAllowOverride(true);
$mockedService = $this->mockService();
$services->setFactory(MyService::class, function() use ($mockedService) {
$mockedService->reveal();
});
$services->setAllowOverride(false);
}
Now you can still mock only the required service. Adding expectations in the test case is still as flexible as it should be:
public function testMyCase()
{
$expected = ['foo', 'bar'];
$this->mockService()->findAll()->willReturn($expected);
$result = $this->service->findAll();
$this->assertSame($expected, $result);
}
I'm creating a Laravel controller where a Random string generator interface gets injected to one of the methods. Then in AppServiceProvider I'm registering an implementation. This works fine.
The controller uses the random string as input to save data to the database. Since it's random, I can't test it (using MakesHttpRequests) like so:
$this->post('/api/v1/do_things', ['email' => $this->email])
->seeInDatabase('things', ['email' => $this->email, 'random' => 'abc123']);
because I don't know what 'abc123' will be when using the actual random generator. So I created another implementation of the Random interface that always returns 'abc123' so I could assert against that.
Question is: how do I bind to this fake generator at testing time? I tried to do
$this->app->bind('Random', 'TestableRandom');
right before the test, but it still uses the actual generator that I register in AppServiceProvider. Any ideas? Am I on the wrong track completely regarding how to test such a thing?
Thanks!
You have a couple options:
Use a conditional to bind the implementation:
class AppServiceProvider extends ServiceProvider {
public function register() {
if($this->app->runningUnitTests()) {
$this->app->bind('Random', 'TestableRandom');
} else {
$this->app->bind('Random', 'RealRandom');
}
}
}
Second option is to use a mock in your tests
public function test_my_controller () {
// Create a mock of the Random Interface
$mock = Mockery::mock(RandomInterface::class);
// Set our expectation for the methods that should be called
// and what is supposed to be returned
$mock->shouldReceive('someMethodName')->once()->andReturn('SomeNonRandomString');
// Tell laravel to use our mock when someone tries to resolve
// an instance of our interface
$this->app->instance(RandomInterface::class, $mock);
$this->post('/api/v1/do_things', ['email' => $this->email])
->seeInDatabase('things', [
'email' => $this->email,
'random' => 'SomeNonRandomString',
]);
}
If you decide to go with the mock route. Be sure to checkout the mockery documentation:
http://docs.mockery.io/en/latest/reference/expectations.html
From laracasts
class ApiClientTest extends TestCase
{
use HttpMockTrait;
private $apiClient;
public function setUp()
{
parent::setUp();
$this->setUpHttpMock();
$this->app->bind(ApiConfigurationInterface::class, FakeApiConfiguration::class);
$this->apiClient = $this->app->make(ApiClient::class);
}
/** #test */
public function example()
{
dd($this->apiClient);
}
}
results
App\ApiClient^ {#355
-apiConfiguration: Tests\FakeApiConfiguration^ {#356}
}
https://laracasts.com/discuss/channels/code-review/laravel-58-interface-binding-while-running-tests?page=1&replyId=581880
Lets assume I have an interface like so:
interface RepositoryInterface{
public function getById($id);
}
This interface gets implemented by X amount of classes.
As an example:
class SqliteRepository implements RepositoryInterface{
public function getById($id)
{
return $id;
}
}
I also have a config file in the config folder(do note, this is not the database.php file, it's whole different file):
'default' => 'sqlite',
'connections' => [
'sqlite' => [
'database' => env('DB_DATABASE', storage_path('database.sqlite')),
],
'some_other_db' => [
'database' => env('DB_DATABASE', storage_path('some_other_db')),
],
],
The connections itself can be anything. A database, an API, heck even a csv file.
The main idea behind this is that I can switch in between storage mediums simply by changing the config. Don't ask me why I'm not using the default laravel database file, it's a long story.
The problem:
I want to be able to inject different implementations of the RepositoryInterface into controllers based on that config file, something along the lines of this:
if(Config::get('default') == 'sqlite')
{
// return new SqliteRepository
}
Obviously the way to go here would be Service Providers. However I'm not exactly sure how to approach this issue.
I mean I could do something along the lines of this:
class RepositoryServiceProvider extends ServiceProvider
{
public function register()
{
if(Config::get('storage') == 'sqlite')
{
$this->app->singleton(SqliteRepository::class, function ($app) {
return new SqliteRepository(config('SqliteRepository'));
});
}
}
}
But it feels a little wrong, not to mention that it gives me zero error control. I don't want to be throwing errors in the ServiceProvider. I need some sort of contextual binding or something along those lines. I have read the the documentation regarding contextual binding but it's no exactly what I'm looking for, as it refers rather to concrete implementations of classes based on what controller uses them.
I was thinking more of an abstract factory type of deal, but, again, I'm not sure how to fit into laravel's way of doing things.
Any pointing in the right direction is appreciated.
interface RepositoryInterface{
public function getById();
}
...
...
class SqliteRepository implements RepositoryInterface{
public function getById()
{
return 1;
}
}
...
...
class CsvRepository implements RepositoryInterface{
public function getById()
{
return 2;
}
}
...
...
class MonkeyPooRepository implements RepositoryInterface{
public function getById()
{
return 3;
}
}
...
...
use RepositoryInterface;
class Controller {
public function __construct( RepositoryInterface $repo ) {
$this->repo = $repo;
}
public function index()
{
dd($this->repo->getById());
}
}
on your app provider;
public function register()
{
$this->app->bind( RepositoryInterface::class, env('REPOSITORY', 'MonkeyPooRepository' ) );
}
index method would return (int)3
I'm trying to seed my DB but I have a problem when I use modelName::create() or $modelName->save().
I have this kind of error
{"error":{"type":"Symfony\Component\Debug\Exception\FatalErrorException","message":"Call to undefined method Doc::save()","file":"/Applications/MAMP/htdocs/Doc_project/app/database/seeds/DatabaseSeeder.php","line":45}
or
Call to undefined method Doc::create()
but i dont know why.
My Model :
class Doc extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'doc';
protected $fillable = array('creators_name', 'type_document', 'title', 'description');
public function steps(){
return this->hasMany('Step')
}
public function tags(){
return this->belongsToMany('Tag', 'doc_tag', 'id_doc', 'id_tag')
}
}
My Seeder :
class DatabaseSeeder extends Seeder {
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Eloquent::unguard();
$this->call('DocAppSeeder');
$this->command->info('Doc app seeds finished.'); // show information in the command lin }
}
}
class DocAppSeeder extends Seeder {
public function run(){
DB::table('doc')->delete();
DB::table('step')->delete();
DB::table('tag')->delete();
DB::table('doc_tag')->delete();
/* DB::table('doc')->insert(array(
'creators_name' => 'Alexis',
'type_document' => 'Solution',
'title' => 'NoAD Printer',
'description' => 'Installation imprimante en NoAd'
));*/
$doc = new Doc;
$doc->creators_name = 'Alexis';
$doc->type_document = 'Solution';
$doc->title = 'NoAD Printer';
$doc->description = 'Installation imprimante en NoAd';
$doc->save();
/*$docTest = Doc::create(array(
'creators_name' => 'Alexis',
'type_document' => 'Solution',
'title' => 'NoAD Printer',
'description' => 'Installation imprimante en NoAd'
));
}
}
I try with DB::Table(...)->insert(...)it works, but I can't use it because I need to get some information on each object
Someone have an idea?
Thank you
It looks like PHP is using a different global level Doc class than the one you think it is. Maybe for a facade or alias (self link, contains details instructions on how to debug facade issues)?
Regardless, the best course of action is to see where PHP thinks this class is. In you seeder, right before save, include the following debugging code
$r = new ReflectionClass('Doc');
var_dump(
$r->getFilename()
);
var_dump(
$r->getName()
);
This will tell you the full-name of the class (if it's an alias) and where the class is defined. My guess is the class you think is Doc is not your Eloquent model, and is, in fact, a different class.
Based on your comments below, it sounds like someone's defined a class named Doc in
/Applications/MAMP/htdocs/Doc_project/app/database/migrations/2014_10_12_201016_doc.php
This isn't normal -- although it's easy to see how it might have happened. You probably used the artisan command to create a migration like this
php artisan migrate:make doc
By not using the more explicate form
php artisan migrate:make create_doc_table
You inadvertently create a migration class with the same name as your model. Try recreating this migration with a less specific name, and you should be all set.
Check your migration file -
Rollback the migration, create a fresh version of migration file and proceed.
It usually happens with artisans when used to create a migration.
For me, i rollback the migration and recrested the migration with another name and it works.