partially mocking a class without affecting the private properties in PHP - laravel

I have a class with a lot of methods in which I need to mock only one method due to some sql incompatibility between mysql and in-memory sqlite database.
class OrderService implements OrderServiceContract
{
protected $deliveryService;
public function __construct(Delivery $deliveryService) // DI injected object
{
...
$this->deliveryService = $deliveryService;
...
}
public function methodNeedstoBeMocked()
{
....some sql related code...
}
public function returnToWarehouse($orderId)
{
DB::transaction(function() use ($orderId) {
...
$this->deliveryService->someOtherMethod($orderId); // problematic external service call
...
});
}
}
Now in my test I partially mock this class according to this doc link, and I call the returnToWarehouse from test but then it says that
Error : Call to a member function returnToWarehouse() on null.
meaning that the property $deliveryService doesn't exist on mock.
My test Implementation is as follows.
/**
* #test
*/
public function an_order_can_be_returned_to_warehouse()
{
...
...
$this->partialMock(OrderService::class, function ($mock) {
$mock->shouldReceive('methodNeedstoBeMocked')->andReturn(collect([]));
});
$orderService = app(OrderService::class);
$orderService->markOrderReturnedToWarehouse($order->id); // here is the problem gets triggered.
...
//assertions
}
What might be going wrong here? and what are some ways to mitigate this? Appreciate your help in advance.

The issue here is that partial test doubles from Mockery do not call the original constructor. For more information, please read the documentation here.
Alternatively, you could consider mocking the "problematic" method a bit differently. For example, you could extract that logic to a repository (since you mention that it is dealing with the database layer) that can then be mocked during your test.

Usually, When I have to mock some third party services I set up a Mock Like this.
This way you can set up easily your DI services
<?php
if (app()->environment('testing')) {
$this->app->bind(Delivery::class, static function () {
$service = \Mockery::mock(Delivery::class);
$service->shouldReceive('someOtherMethod')->andReturn([]);
return $service;
});
}

Related

Mocking a service class inside controller

I am trying to write a Feature test for my controller. To simplify my current situation, imagine my controller looks like this:
public function store(Business $business)
{
try {
(new CreateApplicationAction())->execute($business);
} catch (Exception $e) {
return response()->json(['message' => 'error'], 500);
}
return response()->json(['message' => 'success']);
}
What I am trying to achieve is, instead of testing CreateApplication class logic inside my integration test, I want to write another unit test for it specifically.
Is there a way I can simply say CreateApplicationAction expects execute() and bypass testing inside it? (without executing the code inside execute())
/** #test */
public function can_create_application()
{
$business = Business:factory()->create();
$mock = $this->mock(CreateApplicationAction::class, function (MockInterface $mock) use ($business) {
$mock->shouldReceive('execute')
->once()
->with($business)
->andReturn(true);
});
$response = $this->post('/businesses/3/application', $data);
$response->assertOk();
}
I saw online that people create "MockCreateApplicationAction" class but if possible I don't want to create another class as I don't want any logic to be inside it at all.
Is it possible?
class CreateApplicationAction
{
public function execute($business) {
dd("A");
// Business Logic...
}
}
So when I do the Mock, dd() should never be called. Or I am going in the wrong direction?
You will need to use Laravels container to resolve your class. The basic approach is to use the resolve() method helper. PHP does not have dependency injection, so you need to use one to make it possible, in Laravel the container solves that.
resolve(CreateApplicationAction::class)->execute($business);
On constructors, controller methods, jobs, events, listeners and commands (rule of thumb if the method is named handle), you can inject classes into the parameters and they will resolve through the container.
public function store(Business $business, CreateApplicationAction $applicationAction)
{
try {
$applicationAction->execute($business);

Laravel unit testing automatic dependency injection not working?

With Laravel Framework 5.8.36 I'm trying to run a test that calls a controller where the __construct method uses DI, like this:
class SomeController extends Controller {
public function __construct(XYZRepository $xyz_repository)
{
$this->xyz_repository = $xyz_repository;
}
public function doThisOtherThing(Request $request, $id)
{
try {
return response()->json($this->xyz_repository->doTheRepoThing($id), 200);
} catch (Exception $exception) {
return response($exception->getMessage(), 500);
}
}
}
This works fine if I run the code through the browser or call it like an api call in postman, but when I call the doThisOtherThing method from my test I get the following error:
ArgumentCountError: Too few arguments to function App\Http\Controllers\SomeController::__construct(), 0 passed in /var/www/tests/Unit/Controllers/SomeControllerTest.php on line 28 and exactly 1 expected
This is telling me that DI isn't working for some reason when I run tests. Any ideas? Here's my test:
public function testXYZShouldDoTheThing()
{
$some_controller = new SomeController();
$some_controller->doThisOtherThing(...args...);
...asserts...
}
I've tried things like using the bind and make methods on app in the setUp method but no success:
public function setUp(): void
{
parent::setUp();
$this->app->make('App\Repositories\XYZRepository');
}
That's correct. The whole idea of a unit test is that you mock the dependant services so you can control their in/output consistently.
You can create a mock version of your XYZRepository and inject it into your controller.
$xyzRepositoryMock = $this->createMock(XYZRepository::class);
$some_controller = new SomeController($xyzRepositoryMock);
$some_controller->doThisOtherThing(...args...);
This is not how Laravels service container works, when using the new keyword it never gets resolved through the container so Laravel cannot inject the required classes, you would have to pass them yourself in order to make it work like this.
What you can do is let the controller be resolved through the service container:
public function testXYZShouldDoTheThing()
{
$controller = $this->app->make(SomeController::class);
// Or use the global resolve helper
$controller = resolve(SomeController::class);
$some_controller->doThisOtherThing(...args...);
...asserts...
}
From the docs :
You may use the make method to resolve a class instance out of the
container. The make method accepts the name of the class or interface
you wish to resolve:
$api = $this->app->make('HelpSpot\API');
If you are in a location of your code that does not have access to the
$app variable, you may use the global resolve helper:
$api = resolve('HelpSpot\API');
PS:
I am not really a fan of testing controllers like you are trying to do here, I would rather create a feature test and test the route and verify everything works as expected.
Feature tests may test a larger portion of your code, including how
several objects interact with each other or even a full HTTP request
to a JSON endpoint.
something like this:
use Illuminate\Http\Response;
public function testXYZShouldDoTheThing()
{
$this->get('your/route')
->assertStatus(Response::HTTP_OK);
// assert response content is correct (assertJson etc.)
}

How to mock a paypal transaction in laravel with phpunit?

While testing:
While checkout items from my website, need to mock confirmation... so we can then continue processing the order. Where the testing can be done..
How would i swap out good code for a mock? such as:
$gateway = Omnipay::create('paypal');
$response = $gateway->purchase($request['params'])->send();
if ($response->isSuccessful()) { ... etc ...
How is this possible?
While i have created tests, my knowledge in the area of mocking is basic
As far as it depends t mocking, you don't need to know exact response, you just need to know inputs and outputs data and you should replace your service (Paypal in this case) in laravel service provider. You need some steps like bellow:
First add a PaymentProvider to your laravel service provider:
class AppServiceProvider extends ServiceProvider
{
...
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(PaymentProviderInterface::class, function ($app) {
$httpClient = $this->app()->make(Guzzle::class);
return new PaypalPackageYourAreUsing($requiredDataForYourPackage, $httpClient);
});
}
...
}
Then in your test class you should replace your provider with a mock version of that interface:
class PaypalPackageTest extends TestCase
{
/** #test */
public function it_should_call_to_paypal_endpoint()
{
$requiredData = $this->faker->url;
$httpClient = $this->createMock(Guzzle::class);
$paypalClient = $this->getMockBuilder(PaymentProviderInterface::class)
->setConstructorArgs([$requiredData, $httpClient])
->setMethod(['call'])
->getMock();
$this->instance(PaymentProviderInterface::class, $paypalClient);
$paypalClient->expects($this->once())->method('call')->with($requiredData)
->willReturn($httpClient);
$this->assertInstanceOf($httpClient, $paypalClient->pay());
}
}
This is the approach I usually take when I have to mock methods that contain calls to external libraries (such as Omnipay in your case).
Your snippet isn't very extensive, but I'll assume your class looks something like this:
class PaymentProvider
{
public function pay($request)
{
$gateway = Omnipay::create('paypal');
$response = $gateway->purchase($request['params'])->send();
if ($response->isSuccessful()) {
// do more stuff
}
}
}
What I would do is refactor the class, so that the call to the external library is inside a separate method:
class PaymentProvider
{
protected function purchaseThroughOmnipay($params)
{
$gateway = Omnipay::create('paypal');
return $gateway->purchase($params)->send();
}
public function pay($request)
{
$response = $this->purchaseThroughOmnipay($request['params']);
if ($response->isSuccessful()) {
// do more stuff
}
}
}
Then, after this refactoring, in the test class we can take advantage of the many possibilities PHPunit's getMockBuilder gives us:
<?php
use PHPUnit\Framework\TestCase;
class PaymentProviderTest extends TestCase
{
protected $paymentProvider;
protected function setUp()
{
$this->paymentProvider = $this->getMockBuilder(\PaymentProvider::class)
->setMethods(['pay'])
->getMock();
}
public function testPay()
{
// here we set up all the conditions for our test
$omnipayResponse = $this->getMockBuilder(<fully qualified name of the Omnipay response class>::class)
->getMock();
$omnipayResponse->expects($this->once())
->method('isSuccessful')
->willReturn(true);
$this->paymentProvider->expects($this->once())
->method('purchaseThroughOmnipay')
->willReturn($omnipayResponse);
$request = [
// add relevant data here
];
// call to execute the method you want to actually test
$result = $this->paymentProvider->pay($request);
// do assertions here on $result
}
}
Some explanation of what's happening:
$this->paymentProvider = $this->getMockBuilder(\PaymentProvider::class)
->setMethods(['pay'])
->getMock();
This gives us a mock instance of the Payment class, for which pay is a "real" method whose actual code is actually executed, and all other methods (in our case, purchaseThroughOmnipay is the one we care about) are stubs for which we can override the return value.
In the same way, here we are mocking the response class, so that we can then control its behavoir and influence the flow of the pay method:
$omnipayResponse = $this->getMockBuilder(<fully qualified name of the Omnipay response class>::class)
->getMock();
$omnipayResponse->expects($this->once())
->method('isSuccessful')
->willReturn(true);
The difference here is that we are not calling setMethods, which means that all the methods of this class will be stubs for which we can override the return value (which is exactly what we are doing for isSuccessful).
Of course, in case more methods of this class are called in the pay method (presumably after the if), then you will probably have to use expect more than once.

ZF2 and EntityManager (Doctrine)

I have a problem. I try to get the Entity-Manager without a Controller, but I found no way.
At this time, I get the Entity-Manager like this:
(Controller)
public function getEntityManager()
{
if (null === $this->_em) {
$this->_em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
}
return $this->_em;
}
(Plugin)
public function getEntityManager()
{
if($this->_em == null){
$this->_em = $this->getController()->getServiceLocator()->get('doctrine.entitymanager.orm_default');
}
return $this->_em;
}
You see, I need allways a controller. But, if I need the EntityManager in a model, i have a problem. I can give the model the controller, but I think this is really a bad way.
Have you any idea to get the EntityManager without a controller?
The way I handle Doctrine is through Services, i do it like the following:
//some Controller
public function someAction()
{
$service = $this->getServiceLocator()->get('my_entity_service');
return new ViewModel(array(
'entities' => $service->findAll()
));
}
The Service->findAll() would look something like this:
public function findAll()
{
return $this->getEntityRepository()->findAll();
}
Now we need to define the my_entity_service. I do this inside my Module.php
public function getServiceConfig()
{
return array(
'factories' => array(
'my_entity_service' => 'Namespace\Factory\MyServiceFactory'
)
);
}
Next I create the Factory (note: this could also be done via anonymous function inside the Module.php)
<?php
namespace Namespace\Factory;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
use Namespace\Model\MyModel;
class MyServiceFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$myModel= new MyModel();
$myModel->setEntityManager($serviceLocator->get('Doctrine\ORM\EntityManager'));
return $myModel;
}
}
Now this is a lot to chew :D I totally get that. What is happening here is actually very simple though. Instead of creating your model and somehow get to the EntityManager, you call ZF2's ServiceManager to create the Model for you and inject the EntityManager into it.
If this is still confusing to you, I'll gladly try to explain myself better. For further clarification however I'd like to know about your use case. I.e.: what for do you need the EntityManager or where exactly do u need it.
This code example is outside of the question scope
Just to give you a live example of something I do via ServiceFactories with forms:
public function createService(ServiceLocatorInterface $serviceLocator)
{
$form = new ReferenzwertForm();
$form->setHydrator(new DoctrineEntity($serviceLocator->get('Doctrine\ORM\EntityManager')))
->setObject(new Referenzwert())
->setInputFilter(new ReferenzwertFilter())
->setAttribute('method', 'post');
return $form;
}
Your real question is "How to get an Instance of ServiceManager in my own classes"
For this, take a look at the docu: (bottom of page http://zf2.readthedocs.org/en/latest/modules/zend.service-manager.quick-start.html)
By default, the Zend Framework MVC registers an initializer that will
inject the ServiceManager instance, which is an implementation of
Zend\ServiceManager\ServiceLocatorInterface, into any class
implementing Zend\ServiceManager\ServiceLocatorAwareInterface. A
simple implementation looks like the following.
so implent the ServiceLocatorInterface in your classes and then inside your class you can call:
$this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
or any other service you have registered.

using Observer pattern with a MVC/Codeigniter web site

I have a web site I'm converting to Codeigniter and I want to simplify and decouple. I like what I've read about the Observer pattern for things like "new survey created" (which triggers a new help ticket, which triggers an email, etc).
But how do I implement such a thing in Code Igniter? I see the Symfony component but at this point I'm not concerned about understanding the system as much as figuring out how to use it in controllers and models. I have extended both CI_Model and CI_Controller already for other reasons. Would putting Observer pattern code there be the best?
I imagine a point like this: someone hits the web site and spawns a request which gets routed to a controller/action: http://localhost/test/save_changes
// warning, pseudo-code!
class Test extends MY_Model
{
public function __construct ()
{
// do I put this here?!? - or maybe in MY_Model?
// Should it be a singleton?
$this->load->library('dispatcher');
// where do I attach what I want... here?
$this->load->library('emailer');
$this->dispatcher->attach($this->emailer);
// what if I have 50 possible things that might happen
// based on any given event, from adding a user to
// deleting a survey or document? There has got to be a
// way to attach a bunch of observers that trickle
// down to each object, right?
}
public function save_changes ()
{
$this->load->model('user');
$this->user->init($this->session->userdata('user.id'))->save();
}
}
class User extends MY_Model
{
public function __construct ()
{
parent::__construct ();
// do I put this here?!?
$this->load->library('dispatcher'); // just something to call it
}
public function init($id)
{
if($this->_loadUser ($id))
{
$this->dispatcher->notify($this, 'user.loaded');
}
}
public function save($id)
{
if(parent::save())
{
$this->dispatcher->notify($this, 'user.saved');
}
}
}
class Emailer
{
public function update ($caller,$msg)
{
switch ($msg)
{
case 'user.saved':
// send user an email
// re-cache some stuff
// other things that we might want to do, including more of these:
$this->dispatch->notify('user-saved-email-sent');
break;
}
}
}
class Dispatcher
{
public function notify ($caller, $msg) { ...foreach attached do $obj->update($caller,$msg) ...}
public function attach ($obj) { ... }
public function detach ($obj) { ... }
}
I can see how powerful that would be. But I'm not sure how to simplify the setup and attaching of all of these listener/observers.
Maybe I should have a factory to create them all? It just seems like yes, they would be decoupled from the way it currently works, but it seems managing all the different objects that I'd have to 'attached' in each controller or method would become coupled in a different way.
Thanks,
Hans
Your proposed structure would have to be something like:
$this->load->library('observer_factory', 'of'); // factory for creating observers
// Observer_factory would have knowledge/access to different classes which relate
// to the pattern.
$ync = $this->of->getNotifier( $some_variable ) );
$ync->attach( $this->of->getObserver( $some_other_variable ) );
$ync->attach( $this->of->getObserver( $some_final_variable ) );
$ync->someMethod(); // someMethod calls notify
But I wonder about it. You'd have a factory class that slowly becomes all-knowing. It starts usurping the functionality of the Loader. Why load a library when my Observer_factory can handle it by doing exactly the same thing?
I think you're better off with a library or a model that knows what it is supposed to do and is well designed, then adding this class structure. I do not see the gains outweighing the costs.

Resources