PHPUnit Laravel Testing Controller that uses a Repository - php

I have read so many examples and cannot see what I am doing wrong, please if someone could help.
I am getting an error when running tests (error at the bottom of post), that doens't happen when viewing the page in the browser. I think this is because the repository isn't being instantiated properly so the relevant method not fired? Or some issue with the API call in the mock.
Controller:
namespace ShopApp\Http\Controllers\StoreFront;
use Illuminate\Http\Request;
use ShopApp\Http\Requests;
use ShopApp\Http\Controllers\Controller;
use ShopApp\Repositories\Contracts\CategoryRepositoryContract;
use ShopApp\Repositories\Contracts\PublicationRepositoryContract;
class PagesController extends Controller
{
private $publication;
private $category;
public function __construct(PublicationRepositoryContract $publication, CategoryRepositoryContract $category){
$this->publication = $publication;
$this->category = $category;
}
/**
* Homepage.
* #return view
* #internal param PublicationRepositoryContract $publication
* #internal param CategoryRepositoryContract $category
*/
public function home()
{
$mostRecent = $this->publication->getRecent();
return view('pages/home')->with(compact('mostRecent'));
}
}
Publication Repository:
<?php
namespace ShopApp\Repositories;
use ShopApp\Models\API\APIModel;
use GuzzleHttp\Client as GuzzleClient;
use Illuminate\Support\Facades\Config;
use ShopApp\Repositories\Contracts\PublicationRepositoryContract;
class localPublicationRepository extends APIModel implements PublicationRepositoryContract
{
private $end_point; // where are we talking to?
public $response; //what did we get back?
public function __construct(GuzzleClient $client){
parent::__construct(new $client(['base_uri' => Config::get('customerprovider.local.api.base_uri'), 'http_errors' => true]));
$this->end_point = 'Publications';
}
/**
* Get all publications
*/
public function getAll(){
$this->response = $this->get($this->end_point);
$publications_with_slugs = $this->assembleSlugs($this->response);
return $publications_with_slugs;
}
/**
* Get recent publications
*/
public function getRecent(){
return $this->getAll(); //#todo - update this to just get the most recent
}
}
Test:
<?php
namespace Tests\Unit\Controllers;
use Tests\TestCase;
use Mockery as m;
class PagesControllerTest extends TestCase
{
public $publicationRepositoryContract;
/**
* Setup mocks etc
*/
public function setUp()
{
parent::setup();
$this->publicationRepositoryContract = m::mock('ShopApp\Repositories\Contracts\PublicationRepositoryContract');
}
/**
* Teardown mocks
*/
public function tearDown()
{
m::close();
parent::tearDown();
}
/**
* A basic test example.
*
* #return void
*/
public function testHomepage()
{
$this->publicationRepositoryContract
->shouldReceive('getRecent')
->once();
$this->app->instance('ShopApp\Repositories\Contracts\PublicationRepositoryContract', $this->publicationRepositoryContract);
$response = $this->call('GET', '/');
$response->assertStatus(200);
// getData() returns all vars attached to the response.
$mostRecent = $response->original->getData()['mostRecent'];
$response->assertViewHas('mostRecent');
$this->assertInstanceOf('Array', $mostRecent);
}
}
Test Error:
Expected status code 200 but received 500.
Failed asserting that false is true.
/home/vagrant/Code/imsnews-site/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:61
/home/vagrant/Code/imsnews-site/tests/Unit/Controllers/PagesControllerTest.php:53
Contents of Response ($response->Content()):
<span class="exception_title"><abbr title="ErrorException">ErrorException</abbr> in <a title="/home/vagrant/Code/imsnews-site/storage/framework/views/229655ca372490c9c0b1f5e7e2d4e91e6d3bbf6c.php line 262">229655ca372490c9c0b1f5e7e2d4e91e6d3bbf6c.php line 262</a>:</span>\n
<span class="exception_message">Invalid argument supplied for foreach() (View: /home/vagrant/Code/imsnews-site/resources/views/pages/home.blade.php)</span>\n
Line 262 from home.blade.php:
#foreach ($mostRecent as $key => $publication)
It seems clear that the method ->getRecent(), which in turn, calls ->getAll() on the publications repository is not returning an array as it should, but I don't know why.
Blade isn't complaining about the variable mostRecent not existing, it's complaining about it being invalid in a foreach.
Could this have something to do with Guzzle and the fact it's calling my API from the mocked test object?
Please help, hours have been lost..
Thanks.

Try mocking the concrete repository, and swap it out for the contract in the container. It seems you are mocking the contract, and then swapping it out for the same contract in your container.

TL;DR :
The key was you HAVE to have ->andReturn([]); on the test, like so:
$this->publicationRepositoryContract
->shouldReceive('getRecent')
->once()->andReturn([]);
My test only had:
$this->publicationRepositoryContract
->shouldReceive('getRecent')
->once();
Thanks to Ayo for pointing this out. It only became clear after deleting other parts of my test.

Related

__construct(): Argument #1 ($config) must be of type array, string given

I've created a Laravel project on Laravel 9.20.0 and I'm attempting to use an API wrapper for Linnworks I found on Github (https://github.com/booni3/linnworks-laravel) within my project. I'm okay with raw PHP but admittedly a complete beginner when it comes to Laravel and object oriented PHP.
In my /routes/web.php file I have:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TestController;
Route::get('test', [TestController::class, 'test']);
In /app/Http/Controllers/TestController.php I have:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\OrderService;
class TestController extends Controller
{
protected $orderService;
/**
* Instantiate a new controller instance.
*
* #return void
*/
public function __construct(OrderService $orderService)
{
$this->orderService = $orderService;
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function test()
{
$this->orderService->calculateProfit();
}
}
In /app/Services/OrderService.php I have:
<?php
namespace App\Services;
use Linnworks;
class OrderService {
public function __construct()
{
}
public function calculateProfit()
{
$this->getOrder();
return;
}
public function getOrder()
{
$order = Linnworks::Orders()->GetOrdersByNumOrderId(777678);
echo "<pre>";
print_r($order);
echo "</pre>";
return;
}
}
At this point, I'm just trying to figure out the correct way to use the Linnworks API Wrapper from GitHub within my service class and get it to return something from the Linnworks platform.
I have input the correct API details in the .env file but I'm clearly doing something wrong or missing something obvious (likely due to my lack of experience with object oriented PHP and Laravel) and I'm getting the following error when I load my url /test/:
Booni3\Linnworks\Linnworks::__construct(): Argument #1 ($config) must be of type array, string given, called in /home/username/laravel-projects/project-name/vendor/booni3/linnworks-laravel/src/LinnworksServiceProvider.php on line 49
According to the GitHub readme, usage is as simple as:
$orders = Linnworks::Orders()->getOpenOrders(
25,
1,
null,
null,
'e41b4701-0885-430d-9623-d840d9d46dd6',
null);
Any help or pointers in the right direction will be hugely appreciated as I've hit a bit of a wall.
Thanks in advance

PHP: Mockery Mock variable $user = Auth::user()

So, I am trying to mock a service method.
In my service file:
/**
* Return all Api Keys for current user.
*
* #return Collection
*/
public function getApiKeys(): Collection
{
$user = Auth::user();
return ApiKey::where('org_id', $user->organizationId)->get();
}
How do I mock this?
<?php
namespace App\Services;
use PHPUnit\Framework\TestCase;
use Mockery as m;
class ApiKeysServiceTest extends TestCase
{
public function setUp()
{
parent::setUp();
/* Mock Dependencies */
}
public function tearDown()
{
m::close();
}
public function testGetApiKeys()
{
/* How to test? $user = Auth::user() */
$apiKeysService->getApiKeys();
}
}
In my TestCase class I have:
public function loginWithFakeUser()
{
$user = new GenericUser([
'id' => 1,
'organizationId' => '1234'
]);
$this->be($user);
}
What I want to do is test this method. Maybe this involves restructuring my code so that $user = Auth::user() is not called in the method. If this is the case, any thoughts as to where it should go?
Thanks for your feedback.
In your testGetApiKeys method you're not setting up the world. Make a mock user (using a factory as suggested in the comments factory('App\User')->create()), then setup an apiKey again using the factory, then call the method and assert it's what you've setup. An example with your code
public function loginWithFakeUser()
{
$user = factory('App\User')->create();
$this->be($user);
}
public function testApiSomething()
{
$this->loginWithFakeUser();
// do something to invoke the api...
// assert results
}
A good blueprint for the test structure is:
Given we have something (setup all the needed components)
If the user does some action (visits a page or whatever)
Then ensure the result of the action is what you expect (for example the status is 200)

TYPO3 - how to get FE UID via Viewhelper

How can I fetch and render the uid of the FE User via a Viewhelper? The below is working via a Controller ... but not in a Viewhelper. Where is the difference? I'm using 7.6.11 and at the end I would like to have the uid of the FE User and the usergroup uid of him and further use it in the html of the extension and in general partials ...
/typo3conf/ext/extension/Classes/ViewHelpers/UserViewHelper.php
<?php
namespace Vendor\Extension\ViewHelpers;
class UserViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* User Repository
*
* #var \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository
* #inject
*/
protected $userRepository;
/**
* #var \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserGroupRepository
* #inject
*/
protected $frontendUserGroupRepository;
public function render() {
$userIDTest = $this->userRepository->findByUid($GLOBALS['TSFE']->fe_user->user['uid']);
$this->view->assign('userIDTest', $userIDTest);
}
}
List.html
<f:layout name="Default" />
<f:section name="main">
{userIDTest.uid}
</f:section>
As suggested by Dimitry I replaced
$this->view->assign('userIDTest', $userIDTest);
with
return $userIDTest;
And in List.html I have this:
{namespace custom=Vendor\Extension\ViewHelpers}
<f:layout name="Default" />
<f:section name="main">
<f:alias map="{user: '{custom:user()}'}">
{user.uid} {user.username}
</f:alias>
</f:section>
... and after clearing all Caches (FE/BE/Install) and deleting typo3temp ... now its working!
In 7.x and upwards ViewHelpers are compiled, resulting in the render method being called only once for compiling. Afterwards, only the static method renderStatic() is called. You could overwrite renderStatic, it will be called every time:
<?php
namespace Vendor\Extension\ViewHelpers;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
class UserIdViewHelper extends AbstractViewHelper
{
public function render()
{
return static::renderStatic(
[],
$this->renderChildrenClosure,
$this->renderingContext
);
}
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$userData = $GLOBALS['TSFE']->fe_user->user;
return null !== $userData ? (int)$userData['uid'] : null;
}
}
If you need to use some service in your ViewHelper, things get more complicated, since dependency injection won't work with compiled ViewHelpers. You need to get an object manager, and fetch an instance of the service with the object manager.
This could look like this, assuming you would want to use the FrontendUserRepository as service, because you want to return the entire user object, not only the users uid:
<?php
namespace Vendor\Extension\ViewHelpers;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
class UserViewHelper extends AbstractViewHelper
{
/**
* #var FrontendUserRepository
*/
private static $frontendUserRepository = null;
public function render()
{
return static::renderStatic(
[],
$this->renderChildrenClosure,
$this->renderingContext
);
}
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$userData = $GLOBALS['TSFE']->fe_user->user;
if (null === $userData) {
return null;
}
return static::getFrontendUserRepository()->findByUid((int)$userData['uid']);
}
private static function getFrontendUserRepository()
{
if (null === static::$frontendUserRepository) {
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
static::$frontendUserRepository = $objectManager->get(FrontendUserRepository::class);
}
return static::$frontendUserRepository;
}
}
Disclaimer: All the code is written without actually running it, thus there are bugs in it.
if you want to return the user or the uid of the user in the viewhelper, just return it.
instead of
$this->view->assign('userIDTest', $userIDTest);
do this
return $userIDTest;
In your fluid you can use the user variables in different ways. The easiest one is to use the "alias" viewhelper: https://fluidtypo3.org/viewhelpers/fluid/master/AliasViewHelper.html
<f:alias map="{user: '{namespace:user()}'}">
{user.uid} {user.username}
</f:alias>

Laravel ioc automatic resolution - works from controller but not from custom class

Namespaces omitted for brevity...
I have written the following service provider and registered in config/app.php:
class OfferServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerLossControlManager();
}
protected function registerLossControlManager()
{
$this->app->bind('LossControlInterface', 'LossControl');
}
}
Here is my LossControlInterface
interface LossControlInterface
{
/**
* #param int $demandId
* #param float $offerTotal
* #param float $productTotal
* #param null|int $partnerId
* #return mixed
*/
public function make($demandId, $offerTotal, $productTotal, $partnerId = null);
/**
* #return float
*/
public function getAcceptableLoss();
/**
* #return bool
*/
public function isAcceptable();
/**
* #return bool
*/
public function isUnacceptable();
/**
* #return null
*/
public function reject();
}
Now within the controller, I can inject the LossController as follows:
use LossControlInterface as LossControl;
class HomeController extends BaseController {
public function __construct(LossControl $lossControl)
{
$this->lossControl = $lossControl;
}
public function getLossThresholds()
{
$lossControl = $this->lossControl->make(985, 1000, null);
var_dump('Acceptable Loss: ' . $lossControl->getAcceptableLoss());
var_dump('Actual Loss: ' . $lossControl->calculateLoss());
var_dump('Acceptable? ' . $lossControl->isAcceptable());
}
}
However if I try to dependency inject the LossControlInterface from within a custom class called by a command:
[2014-09-02 13:09:52] development.ERROR: exception 'ErrorException' with message 'Argument 11 passed to Offer::__construct() must be an instance of LossControlInterface, none given, called in /home/vagrant/Code/.../ProcessOffer.php on line 44 and defined' in /home/vagrant/Code/.../Offer.php:79
It appears as though I am unable to dependency inject the interface into a custom class, but I can when dependency injecting into a controller.
Any thoughts on what Im doing wrong or have omitted to get the automatic resolution working?
The IoC is automatic within controllers, and you don't see the injection because Laravel handles the construction of controllers for you. When creating any other custom class by using the new keyword, you will still need to send in all of the parameters needed to it's constructor:
$myClass = new ClassWithDependency( app()->make('Dependency') );
You can hide this, to a degree, by funneling creation of your custom class through a service provider:
// Your service provider
public function register()
{
$this->app->bind('ClassWithDependency', function($app) {
return new ClassWithDependency( $app->make('Dependency') );
});
}
Then just have the IoC make it whenever you need it:
$myClass = app()->make('ClassWithDepenency');
In your case, you can change your code to look like this:
private function setOffer(Offer $offer = null) {
$this->processOffer = $offer ?:
new Offer( app()->make('LossControlInterface') );
}
A perhaps cleaner approach could be to create a service provider and an OfferFactory which gets injected into your controller. The controller can then request the factory to create the offer whenever it needs one:
// Controller
public function __construct(OfferFactory $offerFactory)
{
$this->offerFactory = $offerFactory;
}
public function setOffer(Offer $offer = null)
{
$this->processOffer = $offer ?: $this->offerFactory->createOffer();
}
// OfferFactory
class OfferFactory
{
public function createOffer()
{
return app()->make('Offer');
}
}
This has the benefit of completely decoupling your controller from the logic behind the creation of the offer, yet allowing you to have a spot to add any amount of complexity necessary to the process of creating offers.
In Laravel 5.2 the simplest solution for your particular problem would be to replace
new Offer();
with
App::make('Offer');
or even shorter
app('Offer');
which will use Laravel Container to take care of dependencies.
If however you want to pass additional parameters to the Offer constructor it is necessary to bind it in your service provider
App::bind('Offer', function($app, $args) {
return new Offer($app->make('LossControl'), $args);
});
And voila, now you can write
app('Offer', [123, 456]);
In laravel 5.4 (https://github.com/laravel/framework/pull/18271) you need to use the new makeWith method of the IoC container.
App::makeWith( 'App\MyNameSpace\MyClass', [ $id ] );
if you still use 5.3 or below, the above answers will work.

PHPSpec and Laravel - how to handle double method not found issues

I appear to be having issues with my spec tests when it comes to stubs that are calling other methods.
I've been following Laracasts 'hexagonal' approach for my controller to ensure it is only responsible for the HTTP layer.
Controller
<?php
use Apes\Utilities\Connect;
use \OAuth;
class FacebookConnectController extends \BaseController {
/**
* #var $connect
*/
protected $connect;
/**
* Instantiates $connect
*
* #param $connect
*/
function __construct()
{
$this->connect = new Connect($this, OAuth::consumer('Facebook'));
}
/**
* Login user with facebook
*
* #return void
*/
public function initialise() {
// TODO: Actually probably not needed as we'll control
// whether this controller is called via a filter or similar
if(Auth::user()) return Redirect::to('/');
return $this->connect->loginOrCreate(Input::all());
}
/**
* User authenticated, return to main game view
* #return Response
*/
public function facebookConnectSucceeds()
{
return Redirect::to('/');
}
}
So when the route is initialised I construct a new Connect instance and I pass an instance of $this class to my Connect class (to act as a listener) and call the loginOrCreate method.
Apes\Utilities\Connect
<?php
namespace Apes\Utilities;
use Apes\Creators\Account;
use Illuminate\Database\Eloquent\Model;
use \User;
use \Auth;
use \Carbon\Carbon as Carbon;
class Connect
{
/**
* #var $facebookConnect
*/
protected $facebookConnect;
/**
* #var $account
*/
protected $account;
/**
* #var $facebookAuthorizationUri
*/
// protected $facebookAuthorizationUri;
/**
* #var $listener
*/
protected $listener;
public function __construct($listener, $facebookConnect)
{
$this->listener = $listener;
$this->facebookConnect = $facebookConnect;
$this->account = new Account();
}
public function loginOrCreate($input)
{
// Not the focus of this test
if(!isset($input['code'])){
return $this->handleOtherRequests($input);
}
// Trying to stub this method is my main issue
$facebookUserData = $this->getFacebookUserData($input['code']);
$user = User::where('email', '=', $facebookUserData->email)->first();
if(!$user){
// Not the focus of this test
$user = $this->createAccount($facebookUserData);
}
Auth::login($user, true);
// I want to test that this method is called
return $this->listener->facebookConnectSucceeds();
}
public function getFacebookUserData($code)
{
// I can't seem to stub this method because it's making another method call
$token = $this->facebookConnect->requestAccessToken($code);
return (object) json_decode($this->facebookConnect->request( '/me' ), true);
}
// Various other methods not relevant to this question
I've tried to trim this down to focus on the methods under test and my understanding thus far as to what is going wrong.
Connect Spec
<?php
namespace spec\Apes\Utilities;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use \Illuminate\Routing\Controllers\Controller;
use \OAuth;
use \Apes\Creators\Account;
class ConnectSpec extends ObjectBehavior
{
function let(\FacebookConnectController $listener, \OAuth $facebookConnect, \Apes\Creators\Account $account)
{
$this->beConstructedWith($listener, $facebookConnect, $account);
}
function it_should_login_the_user($listener)
{
$input = ['code' => 'afacebooktoken'];
$returnCurrentUser = (object) [
'email' => 'existinguser#domain.tld',
];
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
$listener->facebookConnectSucceeds()->shouldBeCalled();
$this->loginOrCreate($input);
}
So here's the spec that I'm having issues with. First I pretend that I've got a facebook token already. Then, where things are failing, is that I need to fudge that the getFacebookUserData method will return a sample user that exists in my users table.
However when I run the test I get:
Apes/Utilities/Connect
37 ! it should login the user
method `Double\Artdarek\OAuth\Facade\OAuth\P13::requestAccessToken()` not found.
I had hoped that 'willReturn' would just ignore whatever was happening in the getFacebookUserData method as I'm testing that separately, but it seems not.
Any recommendations on what I should be doing?
Do I need to pull all of the OAuth class methods into their own class or something? It seems strange to me that I might need to do that considering OAuth is already its own class. Is there some way to stub the method in getFacebookUserData?
Update 1
So I tried stubbing the method that's being called inside getFacebookUserData and my updated spec looks like this:
function it_should_login_the_user($listener, $facebookConnect)
{
$returnCurrentUser = (object) [
'email' => 'existinguser#domain.tld',
];
$input = ['code' => 'afacebooktoken'];
// Try stubbing any methods that are called in getFacebookUserData
$facebookConnect->requestAccessToken($input)->willReturn('alongstring');
$facebookConnect->request($input)->willReturn($returnCurrentUser);
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
$listener->facebookConnectSucceeds()->shouldBeCalled();
$this->loginOrCreate($input);
}
The spec still fails but the error has changed:
Apes/Utilities/Connect
37 ! it should login the user
method `Double\Artdarek\OAuth\Facade\OAuth\P13::requestAccessToken()` is not defined.
Interestingly if I place these new stubs after the $this->getFacebookUserData stub then the error is 'not found' instead of 'not defined'. Clearly I don't fully understand the inner workings at hand :D
Not everything, called methods in your dependencies have to be mocked, because they will in fact be called while testing your classes:
...
$facebookConnect->requestAccessToken($input)->willReturn(<whatever it should return>);
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
...
If you don't mock them, phpspec will raise a not found.
I'm not familiar with the classes involved but that error implies there is not method Oauth:: requestAccessToken().
Prophecy will not let you stub non-existent methods.

Categories