Mocking interface dependency of controllers in Laravel testing - php

I'm just trying to test my Laravel app. When coding the project, I was trying to take care about "fat services, skinny controllers" principle, so every logic (including DB logic) is extracted to service classes with interfaces, and injected into the controllers. Then, the dependencies are resolved by IoC container provided by Laravel.
My question is regarding to mocking out these interface dependencies when testing the controllers. To me, it seems like the dependency injection is never properly resolved by the test, it is always using the implementation which is injected by the IoC Container, and not the fake one.
Example Controller Method
public function index(ICrudService $crudService)
{
if (!\Auth::check()) {
return redirect()->route('login');
}
$comps = $crudService->GetAllCompetitions();
return view('competitions.index', ['competitions' => $comps]);
}
setUp method
protected function setUp()
{
parent::setUp();
$this->faker = Faker\Factory::create();
// Create the mock objects
$this->user = \Mockery::mock(User::class);
$this->allComps = new Collection();
for ($i=0; $i<10; $i++)
{
$this->allComps->add(new Competition(['comp_id' => ++$i,
'comp_name' => $this->faker->unique()->company,
'comp_date' => "2018-11-07 17:25:41"]));
}
$this->user->shouldReceive('getAuthIdentifier')
->andReturn(1);
$this->competitionFake = \Mockery::mock(Competition::class);
// Resolve every Competition params with this fake
$this->app->instance(Competition::class, $this->competitionFake);
}
The test
public function test_competition_index()
{
// Mock the Crud Service.
$fakeCrud = \Mockery::mock(ICrudService::class);
// Register the fake Crud Service to DI container.
$this->app->instance(ICrudService::class, $fakeCrud);
// Mock GetALllCompetitions method to return the fake collection.
$fakeCrud->shouldReceive('GetAllCompetitions')
->once()
->andReturn
($this->allComps);
// Authenticate the mock user.
$this->be($this->user);
// Send the request to the route, and assert if the view has competitions array.
$this->actingAs($this->user)->get('competitions')->assertStatus(200);
}
CrudServiceProvider
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind('App\Services\Interfaces\ICrudService', function () {
return new CommonCrudService();
});
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides()
{
return ['App\Services\Interfaces\ICrudService'];
}
The behaviour
The test fails because of HTTP response 500 instead of 200. When debugging, I could see that the controller is still using the CommonCrudService class provided by the ServiceProvider, and not the fake one. If I comment out the CrudServiceProvider, the fake service is being passed to the controller, and returns the collection that I specified. Of course, I want to keep the container for my application.
Had anyone experienced things like this?
Thanks a lot in advance!

Related

How to get Symfony 4 dependency injection working with two different use case scenarios?

We're trying to find the best way to implement dependency injection in a Symfony project with a quite specific problematic.
At user level, our application rely on an "Account" doctrine entity which is loaded with the help of the HTTP_HOST global against a domain property (multi-domain application). Going on the domain example.domain.tld will load the matching entity and settings.
At the devops level, we also need to do batch work with CLI scripts on many accounts at the same time.
The question we are facing is how to write services that will be compatible with both needs?
Let's illustrate this with a simplified example. For the user level we have this and everything works great:
Controller/FileController.php
public function new(Request $request, FileManager $fileManager): Response
{
...
$fileManager->addFile($file);
...
}
Service/FileManager.php
public function __construct(AccountFactory $account)
{
$this->account = $account;
}
Service/AccountFactory.php
public function __construct(RequestStack $requestStack, AccountRepository $accountRepository)
{
$this->requestStack = $requestStack;
$this->accountRepository = $accountRepository;
}
public function createAccount()
{
$httpHost = $this->requestStack->getCurrentRequest()->server->get('HTTP_HOST');
$account = $this->accountRepository->findOneBy(['domain' => $httpHost]);
if (!$account) {
throw $this->createNotFoundException(sprintf('No matching account for given host %s', $httpHost));
}
return $account;
}
Now if we wanted to write the following console command, it would fail because the FileManager is only accepting an AccountFactory and not the Account Entity.
$accounts = $accountRepository->findAll();
foreach ($accounts as $account) {
$fileManager = new FileManager($account);
$fileManager->addFile($file);
}
We could tweak in the AccountFactory but this would feel wrong...
In reality this is even worse because the Account dependency is deeper in services.
Does anyone have an idea how to make this properly ?
As a good practice, you should create an interface for the FileManager and set this FileManagerInterface as your dependency injection (instead of FileManager).
Then, you can have different classes that follow the same interface rules but just have a different constructor.
With this approach you can implement something like:
Service/FileManager.php
interface FileManagerInterface
{
// declare the methods that must be implemented
public function FileManagerFunctionA();
public function FileManagerFunctionB(ParamType $paramX):ReturnType;
}
FileManagerInterface.php
class FileManagerBase implements FileManagerInterface
{
// implement the methods defined on the interface
public function FileManagerFunctionA()
{
//... code
}
public function FileManagerFunctionB(ParamType $paramX):ReturnType
{
//... code
}
}
FileManagerForFactory.php
class FileManagerForFactory implements FileManagerInterface
{
// implement the specific constructor for this implementation
public function __construct(AccountFactory $account)
{
// your code here using the account factory object
}
// additional code that is needed for this implementation and that is not on the base class
}
FileManagerAnother.php
class FileManagerForFactory implements FileManagerInterface
{
// implement the specific constructor for this implementation
public function __construct(AccountInterface $account)
{
// your code here using the account object
}
// additional code that is needed for this implementation and that is not on the base class
}
Ans last but not least:
Controller/FileController.php
public function new(Request $request, FileManagerInterface $fileManager): Response
{
// ... code using the file manager interface
}
Another approach that also looks correct is, assuming that FileManager depends on an AccountInstance to work, changes could be made to your FileManager dependency to have the AccountInstance as a dependency instead of the Factory. Just Because in fact, the FileManager does not need the factory, it needs the result that the factory generates, so, automatically it is not FileManager's responsibility to carry the entire Factory.
With this approach you will only have to change your declarations like:
Service/FileManager.php
public function __construct(AccountInterface $account)
{
$this->account = $account;
}
Service/AccountFactory.php
public function createAccount():AccountInterface
{
// ... your code
}

Object Mocking - How to replace a factory with a service in the service manager?

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);
}

Laravel caching authenticated user's relationships

In my application I use Laravel's authentication system and I use dependency injection (or the Facade) to access the logged in user. I tend to make the logged in user accessible through my base controller so I can access it easily in my child classes:
class Controller extends BaseController
{
protected $user;
public function __construct()
{
$this->user = \Auth::user();
}
}
My user has a number of different relationships, that I tend to eager load like this:
$this->user->load(['relationshipOne', 'relationshipTwo']);
As in this project I'm expecting to receive consistently high volumes of traffic, I want to make the application run as smoothly and efficiently as possible so I am looking to implement some caching.
I ideally, need to be able to avoid repeatedly querying the database, particularly for the user's related records. As such I need to look into caching the user object, after loading relationships.
I had the idea to do something like this:
public function __construct()
{
$userId = \Auth::id();
if (!is_null($userId)) {
$this->user = \Cache::remember("user-{$userId}", 60, function() use($userId) {
return User::with(['relationshipOne', 'relationshipTwo'])->find($userId);
});
}
}
However, I'm unsure whether or not it's safe to rely on whether or not \Auth::id() returning a non-null value to pass authentication. Has anyone faced any similar issues?
I would suggest you used a package like the following one. https://github.com/spatie/laravel-responsecache
It caches the response and you can use it for more than just the user object.
Well, after some messing about I've come up with kind of a solution for myself which I thought I would share.
I thought I would give up on caching the actual User object, and just let the authentication happen as normal and just focus on trying to cache the user's relations. This feels like quite a dirty way to do it, since my logic is in the model:
class User extends Model
{
// ..
/**
* This is the relationship I want to cache
*/
public function related()
{
return $this->hasMany(Related::class);
}
/**
* This method can be used when we want to utilise a cache
*/
public function getRelated()
{
return \Cache::remember("relatedByUser({$this->id})", 60, function() {
return $this->related;
});
}
/**
* Do something with the cached relationship
*/
public function totalRelated()
{
return $this->getRelated()->count();
}
}
In my case, I needed to be able to cache the related items inside the User model because I had some methods inside the user that would use that relationship. Like in the pretty trivial example of the totalRelated method above (My project is a bit more complex).
Of course, if I didn't have internal methods like that on my User model it would have been just as easy to call the relationship from outside my model and cache that (In a controller for example)
class MyController extends Controller
{
public function index()
{
$related = \Cache::remember("relatedByUser({$this->user->id})", 60, function() {
return $this->user->related;
});
// Do something with the $related items...
}
}
Again, this doesn't feel like the best solution to me and I am open to try other suggestions.
Cheers
Edit: I've went a step further and implemented a couple of methods on my parent Model class to help with caching relationships and implemented getter methods for all my relatonships that accept a $useCache parameter, to make things a bit more flexible:
Parent Model class:
class Model extends BaseModel
{
/**
* Helper method to get a value from the cache if it exists, or using the provided closure, caching the result for
* the default cache time.
*
* #param $key
* #param Closure|null $callback
* #return mixed
*/
protected function cacheRemember($key, Closure $callback = null)
{
return Cache::remember($key, Cache::getDefaultCacheTime(), $callback);
}
/**
* Another helper method to either run a closure to get a value, or if useCache is true, attempt to get the value
* from the cache, using the provided key and the closure as a means of getting the value if it doesn't exist.
*
* #param $useCache
* #param $key
* #param Closure $callback
* #return mixed
*/
protected function getOrCacheRemember($useCache, $key, Closure $callback)
{
return !$useCache ? $callback() : $this->cacheRemember($key, $callback);
}
}
My User class:
class User extends Model
{
public function related()
{
return $this->hasMany(Related::class);
}
public function getRelated($useCache = false)
{
return $this->getOrCacheRemember($useCache, "relatedByUser({$this->id})", function() {
return $this->related;
});
}
}
Usage:
$related = $user->getRelated(); // Gets related from the database
$relatedTwo = $user->getRelated(true); // Gets related from the cache if present (Or from database and caches result)

Why does laravel IoC does not provisioning my class with my method?

I can't get why laravel tries to create my class itself, without using my method. I can see that IoC binding is executed (POINT 1 is shown). But singleton method is being never executed. Why?
In my service provider (not deferred):
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
echo "POINT 1"; // I can see this one
$this->app->singleton(\App\Services\FooBar::class, function($app)
{
echo "POINT 2\n"; // Does not comes here
return new FooBar($params);
});
}
I try to use typehinting to resolve dependencies when creating a class:
class Test
{
public function __construct(FooBar $fooBar)
{
}
}
I see that laravel tries to create FooBar to inject it, but can't resolve FooBar's dependencies. They could be resolved, if laravel would call service provider callback, but it does not. Why? How to make laravel use that callback for that class?
Instead of closure (that will not work), use boot() method to initiate your service.
/**
* #param \App\Services\FooBar $foobar
*/
public function boot(\App\Services\FooBar $foobar)
{
$foobar->setOptions(['option' => 'value']);
}
It will launch right after service will be instantiated.
It is because when you are binding a class to IoC container you are not immediately calling the closure. Instead when you need to actually do some action on your class from container you call App::make('class') which would fire the closure and give you the value that was returned from it. So for example
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
echo "POINT 1"; // I can see this one
$this->app->singleton(\App\Services\FooBar::class, function($app)
{
echo "POINT 2\n"; // Does not comes here
return new FooBar($params);
});
$this->app->make(\App\Services\FooBar::class); //here POINT 2 will be called first.
}

What is the proper way to abstract common functionality in Symfony 2 controllers

We have a fairly large symfony2 code base. Generally our Controller actions would look something like
public function landingPageAction(Request $request) {
//do stuff
return $this->render("view_to_render", $template_data);
}
We have two functionalities that are very generic between all of our controllers:
We tend to pass Controller level template parameters to all of the actions in a specific controller - let's call these "Default Parameters"
We set HTTP cache headers at the end of each Action
Understandably we want to abstract this logic away. In doing so we came up with two approaches. We are not certain which approach is better, both in terms of general OO and SOLID principles, but also in terms of performance and how SF2 recommends things be done.
Both approaches rely on having the controller extend an interface that indicates if the controller has "Default Parameters" (later we are considering also adding Cacheable interface)
use Symfony\Component\HttpFoundation\Request;
interface InjectDefaultTemplateVariablesController {
public function getDefaultTemplateVariables(Request $request);
}
Approach 1
This approach is based on events. We define an object that will store our template variables, as well as (in the future) cache indicators
class TemplateVariables {
protected $template_name;
protected $template_data;
public function __construct($template_name, $template_data) {
$this->template_name = $template_name;
$this->template_data = $template_data;
}
/**
* #param mixed $template_data
* #return $this
*/
public function setTemplateData($template_data) {
$this->template_data = $template_data;
return $this;
}
/**
* #return mixed
*/
public function getTemplateData() {
return $this->template_data;
}
/**
* #param mixed $template_name
* #return $this
*/
public function setTemplateName($template_name) {
$this->template_name = $template_name;
return $this;
}
/**
* #return mixed
*/
public function getTemplateName() {
return $this->template_name;
}
}
We also define events that will be triggered on render and which call the views
class InjectDefaultTemplateVariablesControllerEventListener {
/** #var DelegatingEngine */
private $templating;
private $default_template_variables;
public function __construct($templating) {
$this->templating = $templating;
}
public function onKernelController(FilterControllerEvent $event) {
$controller = $event->getController();
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof InjectDefaultTemplateVariablesController) {
$this->default_template_variables = $controller[0]->getDefaultTemplateVariables($event->getRequest());
}
}
public function onKernelView(GetResponseForControllerResultEvent $event) {
$controller_data = $event->getControllerResult();
if ($controller_data instanceof TemplateVariables) {
$template_data = (array)$controller_data->getTemplateData();
$template_data = array_merge($this->default_template_variables, $template_data);
$event->setResponse($this->templating->renderResponse($controller_data->getTemplateName(), $template_data));
}
}
}
Finally our Action now becomes
public function landingPageAction(Request $request) {
//do stuff
return new TemplateVariables("view_to_render", $template_data);
}
Approach 2
This approach is based on putting the common logic into a BaseController from which every other controller inherits. We are still keeping the approach of having Child controllers also extend an interface in case they want to use "Default Parameters".
The following is the new method in the base controller that determines if Default Parameters need to be merged with the specific template parameters. Later this method will also handle cache headers using ttl parameter.
public function renderWithDefaultsAndCache($view, array $parameters = array(), Response $response = null, $ttl = null)
{
$default_template_variables = array(); 
if ($this instanceof InjectDefaultTemplateVariablesController ) {
$default_template_variables = $this->getDefaultTemplateVariables();
}
$template_data = array_merge($default_template_variables, $parameters);
return $this->render($view, $template_data, $response);
}
Action now becomes
public function landingPageAction(Request $request) {
//do stuff
return $this->renderWithDefaultsAndCache("view_to_render", $template_data);
}
Discussion
So far the main arguments for the first approach were that it follows SOLID principles and is easier to extend - iin case more common logic were to be added, it can be put directly into Event Listeners without affecting the controllers.
The main arguments for the second approach were that the logic we are trying to abstract away actually does belong to the controller and not an external event. In addition there was a concern that using events in this manner will result in a poor performance.
We would be really grateful to hear from the experts on which approach is better or possibly suggest a third one that we have missed.
Thank you!
First off I am in no way claiming to be a Symfony 2 architecture expert.
I have a game schedule program which outputs a number of different types of schedules (public, team, referee etc). The various schedules are all similar in that they deal with a set of games but vary in details. The schedules need to be displayed in various formats (html,pdf,xls etc). I also wanted to be able to further tweak things for individual tournaments.
I originally used your second approach by creating a ScheduleBaseController and then deriving various individual schedule controllers from it. It did not work well. I tried to abstract common functionality but the schedules were just different enough that common functionality became complicated and difficult to update.
So I went with an event driven approach very similar to yours. And to answer one of your questions, adding some event listeners will not have any noticeable impact on performance.
Instead of focusing on template data I created what I call an Action Model. Action models are responsible for loading the games based on request parameters and (in some cases) updating the games themselves based on posted data.
Action models are created in the Controller event listener, stored in the request object and then passed to the controller's action method as an argument.
// KernelEvents::CONTROLLER listener
$modelFactoryServiceId = $request->attributes->get('_model');
$modelFactory = $this->container->get($modelFactoryServiceId);
$model = $modelFactory->create($request);
$request->attributes->set('model',$model);
// Controller action
public function action($request,$model)
{
// do stuff
// No template processing at all, just return null
return null;
}
// KernelEvents::VIEW listener
$model = $request->attributes->get('model')
$response = $view->renderResponse($model);
So the controller is mostly responsible for form stuff. It can get data from the model if need be but let's the model handle most of the data related stuff. The controller does no template processing stuff at all. It just returns null which in turn kicks off a VIEW event for rendering.
Lot's of objects? You bet. The key is wiring this up in the route definition:
// Referee Schedule Route
cerad_game__project__schedule_referee__show:
path: /project/{_project}/schedule-referee.{_format}
defaults:
_controller: cerad_game__project__schedule_referee__show_controller:action
_model: cerad_game__project__schedule_referee__show_model_factory
_form: cerad_game__project__schedule_referee__show_form_factory
_template: '#CeradGame\Project\Schedule\Referee\Show\ScheduleRefereeShowTwigPage.html.twig'
_format: html
_views:
csv: cerad_game__project__schedule_referee__show_view_csv
xls: cerad_game__project__schedule_referee__show_view_xls
html: cerad_game__project__schedule_referee__show_view_html
requirements:
_format: html|csv|xls|pdf
Each part is broken up into individual services which, for me at least, makes it easier to customize individual sections and to see what is going on. Is it a good approach? I don't really know but it works well for me.

Categories