Mockery, App Instance not working on some laravel tests - php

Basically, my question is; Why does the $this->app->instance() Call work on one instance of the mocked object, but the other doesn't...
In the example below, the getGroupSingleSignOnLink function actually gets called, the other is mocked and the test passes...
TEST
namespace Tests\Feature;
use App\Models\Group;
use App\Models\User;
use Tests\TestCase;
use App\Clients\SingleSignOnApi;
use Mockery;
class SingleSignOnTest extends TestCase
{
private $validUrl = 'http://www.google.com';
public function setUp()
{
parent::setUp();
$single_sign_on = Mockery::mock(SingleSignOnApi::class);
$single_sign_on->shouldReceive('getGroupSingleSignOnLink')->andReturn($this->validUrl);
$single_sign_on->shouldReceive('getSingleSignOnLink')->andReturn($this->validUrl);
$this->app->instance(SingleSignOnApi::class, $single_sign_on);
}
//THIS TEST FAILS, SingleSignOnApi Class Not Mocked
public function testGroupAuthConnection()
{
$group = Group::whereNotNull('external_platform_key')->first();
$user = $group->users()->first();
$this->be($user);
$group_sso = $group->groupAuthConnections()->first();
$response = $this->get(route('sso.group.connect', ['id' => $group_sso->id]));
$response->assertRedirect($this->validUrl);
$response->assertSessionMissing('__danger');
}
//THIS TEST PASSES, The SingleSignOnApi Class is Mocked
public function testAuthConnectionConnect()
{
$user = User::first();
$this->be($user);
$sso = $user->authConnections()->firstOrFail();
$response = $this->get(route('sso.connect', ['id' => $sso->id]));
$response->assertRedirect($this->validUrl);
$response->assertSessionMissing('__danger');
}
}
CONTROLLER FUNC - TEST MOCK WORKING
public function connect($id)
{
$auth_connection = $this->findAuthConnection($id, Auth::user());
$sso_client = App::make(SingleSignOnApi::class);
$url = $sso_client->getSingleSignOnLink($auth_connection);
return redirect($url);
}
CONTROLLER FUNC - TEST MOCK NOT WORKING
public function connect($id)
{
$group_ids = Auth::user()->groups()->pluck('groups.id')->toArray();
$group_auth_connection = $this->findGroupAuthConnection($id, Auth::user());
//This is the Mocked Object in my Test: SingleSignOnApi
$sso_client = App::make(SingleSignOnApi::class, [$group_auth_connection->group->external_platform_key]);
$url = $sso_client->getGroupSingleSignOnLink($group_auth_connection, Auth::user());
return redirect($url);
}

I'll use Quickbooks as an example to illustrate how I got this to work consistently for me.
Here's my AppServiceProvider, defining a custom QuickbooksSDK Class I created:
...
public function boot()
{
$this->app->bind(QuickbooksSDK::class, function($app) {
return new QuickbooksSDK(
new GuzzleHttp\Client([
'base_uri' => config('invoicing.baseUri'),
'timeout' => 3,
'headers' => [
'Authorization' => 'Bearer '.config('invoicing.apiKey'),
'Accept' => 'application/json'
],
'http_errors' => false
]),
config('invoicing.apiKey'),
env('QUICKBOOKS_REFRESH_TOKEN'),
env('QUICKBOOKS_CLIENT_ID'),
env('QUICKBOOKS_CLIENT_SECRET')
);
});
}
Then I created a second custom class, called the QuickbooksInvoicingDriver, that takes the instantiated SDK Class from the Service container:
public function __construct()
{
$this->api = app(QuickbooksSDK::class);
}
Finally, in my test class, I can mock the QuickbooksSDK, with my own custom responses, to make testing easier:
$vendorResponse = '{"Vendor": {"Balance": 0,"Vendor1099": false,"CurrencyRef": {"value": "GYD","name": "Guyana Dollar"},"domain": "QBO","sparse": false,"Id": "25","SyncToken": "0","MetaData": {"CreateTime": "2018-04-04T12:36:47-07:00","LastUpdatedTime": "2018-04-04T12:36:47-07:00"},"DisplayName": "daraul","PrintOnCheckName": "daraul","Active": true},"time": "2018-04-04T12:36:47.576-07:00"}';
$mock = new MockHandler([
new Response(200, [], $vendorResponse),
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$api = new QuickbooksSDK(
$client,
'test',
'test',
'test',
'test'
);
$this->app->instance(QuickbooksSDK::class, $api);
Now I can run my tests normally, without worrying about third parties. These links were really helpful for me:
http://docs.guzzlephp.org/en/stable/testing.html
https://laravel.com/docs/5.5/container
Let me know if this was helpful.

Related

Model trait causing SQL error during insert

I have an app model that uses a trait. I need the trait to receive some data and using it in the accessor function.
Content model:
use App\Traits\ArchiveTrait;
class Content extends Model
{
use ArchiveTrait;
protected $fillable = ['title','details'];
protected $table = 'contents';
public function getFileName($file_name)
{
return $this->archiving->url.'/media/contents/'.$file_name;
}
}
Archive Trait
trait ArchiveTrait {
private $app_id;
private $archiving;
public function __construct()
{
$this->app_id = config('archiving.id');
$this->archiving = Application::findOrFail($this->app_id);
}
public function guzzleClient() {
$headers = [
'Content-Type' => 'application/json',
'X-Requested-With' => 'XMLHttpRequest',
'Authorization' => 'Bearer ' . $this->archiving->token,
];
$client = new \GuzzleHttp\Client([
'headers' => $headers,
'http_errors' => false
]);
return $client;
}
}
Then problem is, when I try to insert a new content, I get the 'Field 'title' and 'details' doesn't have a default value' SQL error but if I remove the 'use ArchiveTrait' and the getFileName function from the model, then it works just fine. I think there's something wrong with the trait.
Laravel Eloquent models get their attributes passed through via the constructor. Because you are overriding the constructor and not passing anything to the parent constructor, the attributes of your model are never set.
You shouldn't add a constructor in a trait, but if you really needed to, you could fix this by passing the $attributes variable to the parent constructor:
public function __construct($attributes = [])
{
parent::__construct($attributes);
$this->app_id = config('archiving.id');
$this->archiving = Application::findOrFail($this->app_id);
}

Laravel SOAP Server & Client: Method not allowed

I try to setup a SOAP Server for Laravel Framework 5.5.13. Therefore I have created two classes (Server.php and Client.php) and two Controllers, SoapServerController and SoapClientController.
Here's the source code:
app/Classes/Soap/Server.php
namespace App\Classes\Soap;
class Server {
public function __construct() {
}
public function getDate() {
return date('Y-m-d');
}
}
app/Classes/Soap/Client.php
namespace App\Classes\Soap;
class Client {
protected $instance;
public function __construct() {
$params = array( 'uri' => '/soap/server',
'location' => url('/soap/server'),
'trace' => 1,
'soap_version' => SOAP_1_2
);
$this->instance = new \SoapClient( null, $params );
}
public function getDate() {
return $this->instance->getDate();
}
}
app/Http/Controllers/SoapServerController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Classes\Soap;
class SoapServerController extends Controller
{
public function index() {
$params = array( 'uri' => url('/soap/server') );
$server = new \SoapServer( null, $params );
$server->setClass( Soap\Server::class );
$server->handle();
}
}
app/Http/Controllers/SoapClientController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Classes\Soap;
class SoapClientController extends Controller
{
public function index() {
$client = new Soap\Client;
$client->getDate();
}
}
When I open the route /api/soap/client, I get the error:
SoapFault: Method Not Allowed
Do I have to change something in my routes file?
It's important to note that SOAP calls are all supposed to be performed with POST requests. Most likely, you have your route set up with only GET requests, hence the method (POST) is not allowed.

Setter in Guzzle 5 is not allowed in Guzzle6

I'm using Guzzle to make tests on the API i'm working on in Symfony2.
I've been watching the knpUniversity series on Rest, and i have trouble updating the part where i should be able to use the app_test.php instead of app.php.
In the series, they use Guzzle v5, but in v6 there are major changes with PSR7 use
In the tutorial, to permit the use of another uri ending with other than app.php, they use :
use GuzzleHttp\Event\BeforeEvent;
...
class ApiTestCase extends KernelTestCase
{
...
public static function setUpBeforeClass()
{
$baseUrl = getenv('TEST_BASE_URL');
self::$staticClient = new Client([
'base_url' => $baseUrl,
'defaults' => [
'exceptions' => false
]
]);
...
// guaranteeing that /app_test.php is prefixed to all URLs
self::$staticClient->getEmitter()
->on('before', function(BeforeEvent $event) {
$path = $event->getRequest()->getPath();
if (strpos($path, '/api') === 0) {
$event->getRequest()->setPath('/app_test.php'.$path);
}
});
self::bootKernel();
}
But with Guzzle6, there is no setter for the request :
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class ApiTestCase extends KernelTestCase
{
private static $staticClient;
protected $client;
public static function setUpBeforeClass()
{
//defined in phpunit.xml
$baseUri = getenv('TEST_BASE_URI');
//Create a handler stack that has all of the default middlewares attached
$handler = HandlerStack::create();
// guaranteeing that /app_test.php is prefixed to all URLs
$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
$path = $request->getUri()->getPath();
if (strpos($path, '/api') === 0) {
$request->getUri()->setPath('/app_test.php'.$path); // <-- this is not allowed
}
// Notice that we have to return a request object
return $request->withHeader('X-Foo', 'Bar');
}));
// Inject the handler into the client
self::$staticClient = new Client([
'base_uri' => $baseUri.'/app_test.php',
'handler' => $handler,
'defaults' => [
'http_errors' => false
]
]);
//Allows to use service container
self::bootKernel();
}
So how can i change the uri, to use the app_test.php instead of app.php ?
Thanks
===========================
EDIT :
Actually, they give a solution in one of the post of the comment
$handler->push(Middleware::mapRequest(function(RequestInterface $request) {
$path = $request->getUri()->getPath();
if (strpos($path, '/app_test.php') !== 0) {
$path = '/app_test.php' . $path;
}
$uri = $request->getUri()->withPath($path);
return $request->withUri($uri);
}));
Thanks #Cerad

Laravel – Calling API connections

Repeated Calls – Lets say you need your Application to talk to an API and you're using guzzle or a wrapper or whatever. I find myself having to call the connection in every controller function e.g:
class ExampleController extends Controller
{
public function one()
{
$client = new Client();
$response = $client->get('http://',
[ 'query' => [ 'secret' => env('SECRET')]]);
$json = json_decode($response->getBody());
$data = $json->object;
// do stuff
}
public function two()
{
$client = new Client();
$response = $client->get('http://',
[ 'query' => [ 'secret' => env('SECRET')]]);
$json = json_decode($response->getBody());
$data = $json->object;
// do stuff
}
}
How do I better handle this? Do I use a service Provider? if so, how would I best implement these calls? Should I create another controller and call all my API connections in each function and then include that controller and call upon each function as required? Should I place it in a __construct?
lets try the Dependency inversion principle
Ok this might sound a bit hard at first and my code might have some typos or minor mistakes but try this
You need to create the interface
namespace app\puttherightnamespace; // this deppends on you
interface ExempleRepositoryInterface
{
public function getquery(); // if you passinga variable -> public function getquery('variable1');
}
Now you have to create the repo
class ExempleRepository implements ExempleRepositoryInterface {
public function getquery() {
$client = new Client();
$response = $client->get('http://',
[ 'query' => [ 'secret' => env('SECRET')]]);
$json = json_decode($response->getBody());
return $json->object;
}
Now last step is to bind the interface to the repo in a service provider register method
public function register()
{
$this->app->bind('namespacehere\ExempleRepositoryInterface', 'namespacehere\ExempleRepository');
}
Now everytime you need the result in a controller all you have to do is to ineject
class ExempleController extends Controller {
private $exemple;
public function __construct(ExempleRepositoryInterface $home) {
$this->exemple = $exemple;
}
public function test() {
$data = $this->exemple->getquery(); / you can pass a variable here if you want like this $this->exemple->getquery('variable');
// do stuff
}
this is not the simplest way but this is the best way i guess

How to get serviceManager to work on a class zf2

I have some services defined on my module.php that works as intended declared as:
public function getServiceConfig()
{
return array(
'factories' => array(
'Marketplace\V1\Rest\Service\ServiceCollection' => function($sm) {
$tableGateway = $sm->get('ServiceCollectionGateway');
$table = new ServiceCollection($tableGateway);
return $table;
},
'ServiceCollectionGateway' => function ($sm) {
$dbAdapter = $sm->get('PdoAdapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new ServiceEntity());
return new TableGateway('service', $dbAdapter, null, $resultSetPrototype);
},
'Marketplace\V1\Rest\User\UserCollection' => function($sm) {
$tableGateway = $sm->get('UserCollectionGateway');
$table = new UserCollection($tableGateway);
return $table;
},
'UserCollectionGateway' => function ($sm) {
$dbAdapter = $sm->get('PdoAdapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new UserEntity());
return new TableGateway('user', $dbAdapter, null, $resultSetPrototype);
},
),
);
}
I use them to map my db tables to an object. From my main classes of my project I can access them without any problem. Take a look of my project file tree:
For example, userResource.php extends abstractResource and this function works:
public function fetch($id)
{
$result = $this->getUserCollection()->findOne(['id'=>$id]);
return $result;
}
Inside ResourceAbstract I have:
<?php
namespace Marketplace\V1\Abstracts;
use ZF\Rest\AbstractResourceListener;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class ResourceAbstract extends AbstractResourceListener implements ServiceLocatorAwareInterface {
protected $serviceLocator;
public function getServiceCollection() {
$sm = $this->getServiceLocator();
return $sm->get('Marketplace\V1\Rest\Service\ServiceCollection');
}
public function getUserCollection() {
$sm = $this->getServiceLocator();
return $sm->get('Marketplace\V1\Rest\User\UserCollection');
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator) {
$this->serviceLocator = $serviceLocator;
}
public function getServiceLocator() {
return $this->serviceLocator;
}
}
There as suggested by the Zf2 documentation I need to implement ServiceLocatorAwareInterface in order to use the serviceManager. So far so good. Then I decided to add a new class, call Auth.
This class is not very different from abstractResource, it gets call in loginController like this:
<?php
namespace Marketplace\V1\Rpc\Login;
use Zend\Mvc\Controller\AbstractActionController;
use Marketplace\V1\Functions\Auth;
class LoginController extends AbstractActionController
{
public function loginAction()
{
$auth = new Auth();
$data = $this->params()->fromPost();
var_dump($auth->checkPassword($data['email'], $data['password']));
die;
}
}
This is Auth:
<?php
namespace Marketplace\V1\Functions;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Auth implements ServiceLocatorAwareInterface {
protected $serviceLocator;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator) {
$this->serviceLocator = $serviceLocator;
}
public function getServiceLocator() {
return $this->serviceLocator;
}
public function checkPassword($email, $rawPassword) {
$user = $this->getServiceLocator()->get('Marketplace\V1\Rest\User\UserCollection')->findByEmail($email);
if($user)
return false;
$result = $this->genPassword($rawPassword, $user->salt);
if($result['password'] === $user->password)
return true;
else
return false;
}
public function genPassword($rawPassword, $salt = null) {
if(!$salt)
$salt = mcrypt_create_iv(22, MCRYPT_DEV_URANDOM);
$options = [
'cost' => 11,
'salt' => $salt,
];
return ['password' => password_hash($rawPassword, PASSWORD_BCRYPT, $options), 'salt' => bin2hex($salt)];
}
}
As you can see it follows the same path that abtractResource do, BUT, in this case when I execute loginController I get an error:
Fatal error</b>: Call to a member function get() on null in C:\WT-NMP\WWW\MarketPlaceApi\module\Marketplace\src\Marketplace\V1\Functions\Auth.php on line 25
And that refers to this line: $user = $this->getServiceLocator()->get('Marketplace\V1\Rest\User\UserCollection')->findByEmail($email);
Meaning that getServiceLocator is empty. Why I cant get serviceLocator to work on Auth class but I can in abstractResource?
That's because the ServiceLocator is injected through a mechanism called 'setter injection'. For that to happen, something (e.g., ServiceManager) needs to call a setter for a class, in this case setServiceLocator. When you directly instantiate Auth that's not the case. You need to add your class to the service locator, for example as an invokable service.
E.g.:
public function getServiceConfig()
{
return array(
'invokables' => array(
'Auth' => '\Marketplace\V1\Functions\Auth',
),
);
}
or, more appriopriatly as it doesn't use a anonymous function for a factory, put it in your modules config file `modules/Marketplace/config/module.config.php':
// ...
'service_manager' => array(
'invokables' => array(
'Auth' => '\Marketplace\V1\Functions\Auth',
),
),
and the you can get Auth from the service locator in your controller:
$auth = $this->getServiceLocator()->get('Auth');
instead of:
$auth = new Auth;
This way the service locator will construct Auth for you, check what interfaces it implements and when it finds out that it does implement ServiceLocatorAwareInterface then it'll run the setter passing an instance of itself into it. Fun fact: the controller itself gets injected an instance of service locator the same way (it's an ancestor of this class implementing the very same interface). Another fun fact: that behaviour may change in future as discussed here.

Categories