I'm creating unit tests in Laravel 8 using phpunit and I have a controller that uses external API to get data for my functions using Guzzle HTTP Client.
I placed the method to call the guzzle inside the Controller.php so it can be accessed in any other controller by extending this class
class Controller extends BaseController
{
protected function getAPI($url, $params = [])
{
$test_api = new Client();
$headers = [
'form_params' => $params,
'http_errors' => false
];
$response = $test_api->get($url, $headers);
// More stuff to get the response...
}
}
On other controllers I called the method to do the API call
class OtherController extends Controller
{
public function someMethod()
{
$response = $this->getAPI('/get_data', [], 'get');
// More stuffs...
}
}
The unit test I'm creating is for someMethod() and I don't know how to mock the API call inside this controller that has been extended on other controller.
What I wanted is to mock the API call so I don't need to actually "call" the API. I checked the documentation of guzzle about testing (https://docs.guzzlephp.org/en/stable/testing.html#mock-handler) but it doesn't make sense on how to implement this on my scenario.
You can use PHPUnit:
use Tests\TestCase;
[...]
public function testApiOtherControllerSomeMethod(){
//Prepare your sample data that mocks API result
//or create a variable with your wanted api mocked data.
$apiTestData = Storage::disk('test-data-api')
->get('apiTestData.txt');
//Using the PHPUnit MockBuilder, target the class and method
//you want to mock. In your case, you mock the inherited method
//"getAPI" from Controller, in OtherController
$apiMock = $this->getMockBuilder(OtherController::class)
->onlyMethods(['getAPI'])
->getMock();
//use the mock object to change the method's return output
$apiMock->expects($this->any())
->method('getAPI')
->willReturn($apiTestData);
//now the mock object will behave like the OtherController class
//and you can now call "someMethod", which will return the mocked
//data when calling getAPI() internally.
$testResult = $apiMock->someMethod();
//$this->assert... your returned mocked data.
}
As a side note, you should probably move the API handling from the main Controller class, into an API service.
Related
I'm using Symfony 5.4 with a custom authenticator which reads & validates a JWT with each request.
Inside the JWT is data which I need accessible in the controller.
Rather than re-read the JWT in the controller, I'd like to store the decoded data, or even 1 element of that data, so that it doesn't need to be re-read in a controller.
What is an efficient way to access data detected in an authenticator, so it is available in the context of a controller action?
I would implement service for this in Symfony, which should be something like this
<?php
namespace App\Service;
use Symfony\Component\HttpFoundation\RequestStack;
class JWTInterceptor
{
protected $request;
protected $data;
public function __construct(RequestStack $requestStack)
{
$this->request = $requestStack->getCurrentRequest();
// Get JWT token from request header, decode and store it in $this->data
}
// Get decoded data
public function getData()
{
return $this->data;
}
}
And in your controller just use Dependency Injection to insert the service and call JWTInterceptor::getData() to use decoded data.
There should be other approach as well, like using EventListener or EventSubscriber or implement a root/base controller with relevant methods and make it accessible to all child controllers etc.
Or if you are using https://github.com/lexik/LexikJWTAuthenticationBundle it already comes packaged with events so you can modify as per your need.
I'm new to PHPUnit and i'm wondering how to assert that a function from a service has been called.
I tried to mock my service that implement the function ofDatetimeRange :
$mock = $this->getMockBuilder(QueryBuilder::class)
->onlyMethods(['ofDatetimeRange'])
->getMock();
And then just call the function that suppose to call the service and finally assert that have been call once or never.
Here is the test case :
/**
* Test that there is no date filter.
*
* #return void
*/
public function testNoDateFilter()
{
$mock = $this->getMockBuilder(QueryBuilder::class)
->onlyMethods(['ofDatetimeRange'])
->getMock();
$engine = new Engine();
$engine->prepareQuery(); <--- this should call QueryBuilder::ofDatetimeRange
$mock->expects($this->exactly(1))->method('ofDatetimeRange');
}
Expectation failed for method name is "ofDatetimeRange" when invoked 1
time(s). Method was expected to be called 1 times, actually called 0
times.
It looks like my engine doesn't use the mocked instance...
Is there something i'm doing wrong ?
Note that $engine->prepareQuery() should call ofDatetimeRange method of QueryBuilder class.
You're right, $engine object in your test doesn't use the mock object.
I'm not sure how exactly Engine class looks like, but to be able to use mock you usualy should design your class to have mocked object dependency.
class Engine
{
private QueryBuilder $qb;
public function __construct(QueryBuilder $qb)
{
$this->qb = $qb;
}
}
And then you pass the mock object in your test case:
$engine = new Engine($mock);
This should work.
I have model class that calls mailer class inside one of its methods:
class someModel{
public function sendEmail($data){
$mailer = new Mailer();
$mailer->setFrom($data['from']);
$mailer->setTo($data['to']);
$mailer->setSubject($data['subject']);
return $mailer->send();
}
}
How can I test sendEmail method? Maybe I should mock mailer class and check if all these mailer methods were called in sendMail method?
Your help would be appreciated.
IMO wrapping the Mailer class does not solve the problem you're facing, which is you don't have control over the Mail instance being used.
The problem comes from creating the dependencies inside the object that needs them instead of injecting them externally like this:
class someModel{
private $mailer;
public function __construct(Mailer $mailer) {
$this->mailer = $mailer;
}
public function sendEmail($data){
$this->mailer->setFrom($data['from']);
$this->mailer->setTo($data['to']);
$this->mailer->setSubject($data['subject']);
return $this->mailer->send();
}
}
When creating the someModel instance, you must pass a Mail instance (which is an external dependency). And in the test you can pass a Mail mock that will check that the correct calls are being made.
Alternative:
If you feel that injecting a Mail instance is bad (maybe because there are lots of someModel instances), or you just can't change your code this way, then you could use a Services repository, that will keep a single Mail instance and that allows you to set it externally (again, in the test you would set a mock).
Try a simple one like Pimple.
I would (and have in my own code with Mailer!) wrap your instance of Mailer inside a class that you write. In other words, make your own Email class that uses Mailer under the hood. That allows you to simplify the interface of Mailer down to just what you need and more easily mock it. It also gives you the ability to replace Mailer seamlessly at a later date.
The most important thing to keep in mind when you wrap classes to hide external dependencies is keep the wrapper class simple. It's only purpose is to let you swap out the Email libraries class, not provide any complicated logic.
Example:
class Emailer {
private $mailer = new Mailer();
public function send($to, $from, $subject, $data) {
$this->mailer->setFrom($from);
$this->mailer->setTo($to);
...
return $mailer->send();
}
}
class EmailerMock extends Emailer {
public function send($to, $from, $subject, $data) {
... Store whatever test data you want to verify ...
}
//Accessors for testing the right data was sent in your unit test
public function getTo() { ... }
...
}
I follow the same pattern for all classes/libraries that want to touch things external to my software. Other good candidates are database connections, web services connections, cache connections, etc.
EDIT:
gontrollez raised a good point in his answer about dependency injection. I failed to explicitly mention it, but after creating the wrapper the way you would want to use some form of dependency injection to get it into the code where you want to use it. Passing in the instance makes it possible to setup the test case with a Mocked instance.
One method of doing this is passing in the instance to the constructor as gontrollez recommends. There are a lot of cases where that is the best way to do it. However, for "external services" that I am mocking I found that method became tedious because so many classes ended up needing the instance passed in. Consider for example a database driver that you want to Mock for your tests, but you use in many many different classes. So instead what I do is create a singleton class with a method that lets me mock the whole thing at once. Any client code can then just use the singleton to get access to a service without knowing that it was mocked. It looked something like this:
class Externals {
static private $instance = null;
private $db = null;
private $email = null;
...
private function __construct() {
$this->db = new RealDB();
$this->mail = new RealMail();
}
static function initTest() {
self::get(); //Ensure instance created
$db = new MockDB();
$email = new MockEmail();
}
static function get() {
if(!self::$instance)
self::$instance = new Externals();
return self::$instance;
}
function getDB() { return $this->db; }
function getMail() { return $this->mail; }
....
}
Then you can use phpunit's bootstrap file feature to call Externals::initTest() and all your tests will be setup with the mocked externals!
First, as RyanW says, you should write your own wrapper for Mailer.
Second, to test it, use a mock:
<?php
class someModelTest extends \PHPUnit_Framework_TestCase
{
public function testSendEmail()
{
// Mock the class so we can verify that the methods are called
$model = $this->getMock('someModel', array('setFrom', 'setTo', 'setSubject', 'send'));
$controller->expects($this->once())
->method('setFrom');
$controller->expects($this->once())
->method('setTo');
$controller->expects($this->once())
->method('setSubject');
$controller->expects($this->once())
->method('send');
$model->sendEmail();
}
}
The above code is untested, but it basically mocks the someModel class, creating dummy functions for each each function called within sendEmail. It then tests to make sure each of the functions called by sendEmail is called exactly once when sendEmail is called.
See the PHPUnit docs for more info on mocking.
New to Symfony2, I'm building an app that uses an external API to get data. I created a lot of client classes to retrieve and transform each entity from the API, and I defined those classes as services - e.g., I have a FooClient with methods like getAll() or getThoseThatInterestMe($me), which return data from the API.
Now I wanted to create a ApiClientFacade class, which acts as an interface in front of all the XxxClient classes, following the Facade Pattern - e.g., this facade class would have a method getAllFoo(), which in turn would call FooClient::getAll(), and so on...
I could define my facade class as a service as well, but it'd have too many dependencies - I have around 30 client classes. Also, afaik with this approach I'd be loading all 30 dependencies every time, while most of the times I'd only need one dependency...
So, is there a better way to do this?
Use additional ApiClientFactory to move responsibility about "instantiation of ApiClient objects" from your ApiFacade class (which is your initial idea, as I understood).
In some pseudo-php code my idea is:
$api = new ApiFacade(new ApiClientFactory);
$api->sendNotificationAboutUserLogin('username', time());
An example of method:
class ApiFacade {
private $apiFactory;
public function __construct(ApiClientFactory $factory)
{
$this->apiFactory = $factory;
}
public function sendNotificationAboutUserLogin($username, $timeOfOperation)
{
return $this->apiFactory
->createApi('User')
->post(
'notifications',
array('operation' => 'login', 'username' => $username, 'timeOfOperation' => $timeOfOperation)
);
}
}
In this case your Facade class stays injectable (testable), but also becomes simpler instantiatable (you don't need to pass all dependencies into it anymore).
The ApiClientFactory should look like that:
class ApiClientFactory {
private $apiBaseUrl;
public function __construct($apiBaseUrl)
{
$this->apiBaseUrl = $apiBaseUrl;
}
public function createApi($apiName)
{
switch ($apiName) {
case 'User': return new \My\UserApi($this->apiBaseUrl);
default: // throw an exception?
}
}
}
I've been reading through the internals chapter in the Symfony2 docs and it says if I add a listener to the kernel.controller event I can swap the controller that gets run, I've got something that works a bit like this:
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
$replacementControllerName = .... //Some logic to work out the name of the new controller
$replacementController = ?? //Not sure what goes here
$event->setController($replacementController);
}
The bit I'm unsure if is once I've worked out the name of the replacement controller, how do I get an instance of it that I can pass to setController?
You can set your controller to any callable, which means something like
A static method array('class', 'method')
An instance method array($instance, 'method')
An anonymous function function() { ... }
A regular global function 'function';
An instance of a class implementing the __invoke() method new MyClassImplementingInvoke()
The special syntax 'class::method' which forces the ControllerResolver to create a new instance of class (calling the constructor without any argument) and returning a callable array($instanceOfClass, 'method')
EDIT:
I looked up the wrong ControllerResolver. When running Symfony in a standard setup it'll use the Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver (and not the Symfony\Component\HttpKernel\Controller\ControllerResolver). So the controller name will be handled a little bit different to what I wrote above.
The following example sums up all the possible options you have when setting your controller.
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
// call method in Controller class in YourBundle
$replacementController = 'YourBundle:Controller:method';
// call method in service (which is a service registered in the DIC)
$replacementController = 'service:method';
// call method on an instance of Class (created by calling the constructor without any argument)
$replacementController = 'Class::method';
// call method on Class statically (static method)
$replacementController = array('Class', 'method');
// call method on $controller
$controller = new YourController(1, 2, 3);
$replacementController = array($controller, 'method');
// call __invoke on $controller
$replacementController = new YourController(1, 2, 3);
$event->setController($replacementController);
}