Symfony, Call service in PHP Unit - php

I have a unit test, where I need to call a service.
I did it that way:
<?php
namespace FM\PriceBundle\Tests\Service;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class PriceServiceTest extends KernelTestCase
{
private $container;
public function testFiulPrice()
{
self::bootKernel();
$this->container = self::$kernel->getContainer();
$productId = 1;
$id = 1;
$what = ['postal', 'departement', 'region', 'country', 'insee'];
$date = new \DateTime('2016-06-23');
$price = $this->container->get('fm.price.get')->fiulPrice($productId, $id, $what[0], $date);
$this->assertNotEmpty($price);
}
}
But I have this error:
There was 1 error:
1) FM\PriceBundle\Tests\Service\PriceServiceTest::testFiulPrice
Predis\Connection\ConnectionException: Connection refused [tcp://localhost:6379]

I guess you use redis in your app.
But I don't understand why do you want to implement test like this.
That test literally takes fixed data, an tries to get a result from external service that uses dependency (redis).
In my opinion, you need unit test for PriceService, another one for serivce registered as 'fm.price.get', and (maybe) the third one that use redis, but in this case you have to populate cache with some sample data.

Related

Test laravel Cache failed

I am trying to test via phpunit laravel cache (file driver). My test body:
namespace Tests\Feature;
use Illuminate\Support\Facades\Cache;
use Tests\TestCase;
use Closure;
class CacheTestTest extends TestCase
{
public function testCache()
{
Cache::shouldReceive('remember')
->once()
->with(md5(1), 120, Closure::class)
->andReturn('Closure');
}
}
When I am running test, I get error:
Method remember('c4ca4238a0b923820dcc509a6f75849b', 120, 'Closure')
from Mockery_0_Illuminate_Cache_CacheManager should be called exactly
1 times but called 0 times.
What am I doing wrong? Where to get docs about shouldReceive and what I must put to the andReturn ?
Thanks for replies!
EDIT:
The code creates Carbon object and then check if it exist.
public function testCache()
{
$key = md5(1);
$duration = 120;
$object = Cache::remember( $key, $duration, function () {
return Carbon::today();
});
Cache::shouldReceive('get')
->with($key, $duration, \Closure::class)
->andReturn($object);
}
So, I can give some tips based on the test you added to your question:
You are trying to test if Cache works
You want to check if calling Cache with the right parameters works
So:
Your test makes no sense at all, because you are testing if Cache works, and it does! It is shipped by the framework itself, so of course it is working. If you want to test a specific caching part of your code, that is fine, but just calling Cache and then checking if it cached, that test adds no value at all
Instead of having Cache::shouldReceive(...)->... after the Cache::remember (the part you want to test), you have to move that BEFORE you call it, because you are literally caching the value on your test environment, but then you are saying "if something calls Cache->get with $key, $duration and \Closure::class, then return $object.
To have 2. fixed, you have to use this code (instead of using a Mock, use a Spy, this is what Laravel says about Spies):
use WithFaker;
public function testCache()
{
$key = $this->faker->uuid();
$duration = $this->faker->numberBetween(1, 120);
$spy = Cache::spy();
Cache::remember(
$key,
$duration,
function () {
return today();
}
);
$spy->shouldHaveReceived('remember')
->once()
->with($key, $duration, \Mockery::type('callable'));
}
You can read more about the difference between a Mock and a Spy, but the long story short is that a Mock will mock what it will return when a certain method of a certain class with more conditions (or not) is met what to return or do, but a Spy will spy (assert) if those conditions were met AFTER you run your code.
The code creates Carbon object and then check if it exist.
public function testCache()
{
$key = md5(1);
$duration = 120;
$object = Cache::remember( $key, $duration, function () {
return Carbon::today();
});
Cache::shouldReceive('get')
->with($key, $duration, \Closure::class)
->andReturn($object);
}

Issue with Mockery when injecting multiple mocked objects into controller

I'm using Mockery in my Laravel based PHP project to help test a Laravel MVC controller. Below is the relevant part of my controller class I'm trying to test.
class DevicesController extends Controller
{
private $deviceModel;
private $rfDeviceModel;
private $userModel;
private $userDeviceModel;
public function __construct(Device $deviceModel, RFDevice $rfDeviceModel, User $userModel, UserDevice $userDeviceModel)
{
$this->middleware('guest');
$this->deviceModel = $deviceModel;
$this->rfDeviceModel = $rfDeviceModel;
$this->userModel = $userModel;
$this->userDeviceModel = $userDeviceModel;
}
...
public function add(Request $request)
{
$name = $request->input('name');
$description = $request->input('description');
$onCode = $request->input('onCode');
$offCode = $request->input('offCode');
$pulseLength = $request->input('pulseLength');
$type = 1;
$currentUserId = $this->currentUser()->id;
$newDeviceId = $this->deviceModel->add($name, $description, $type)->id;
$this->rfDeviceModel->add($onCode, $offCode, $pulseLength, $newDeviceId);
$this->userDeviceModel->add($currentUserId, $newDeviceId);
return redirect()->route('devices');
}
}
In particular, I'm writing several unit tests around the controller's add(Request $request) function to make sure that each of the three model add(...) functions are called. My test case to handle this looks like the following:
public function testAdd_CallsAddForModels()
{
$mockDeviceModel = Mockery::mock(Device::class);
$mockDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(Device::class, $mockDeviceModel);
$mockRFDeviceModel = Mockery::mock(RFDevice::class);
$mockRFDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(RFDevice::class, $mockRFDeviceModel);
$mockUserDeviceModel = Mockery::mock(UserDevice::class);
$mockUserDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(UserDevice::class, $mockUserDeviceModel);
$user = $this->givenSingleUserExists();
$this->addDeviceForUser($user->user_id);
}
private function givenSingleUserExists()
{
$user = new User;
$name = self::$faker->name();
$email = self::$faker->email();
$userId = self::$faker->uuid();
$user = $user->add($name, $email, $userId);
return $user;
}
private function addDeviceForUser($userId)
{
$this->withSession([env('SESSION_USER_ID') => $userId])
->call('POST', '/devices/add', [
'name' => 'Taylor',
'description' => 'abcd',
'onCode' => 1,
'offCode' => 2,
'pulseLength' => 3
]);
}
When I run this test, I get the following output in the console:
There was 1 error:
1) Tests\Unit\Controller\DeviceControllerTest::testAdd_CallsAddForModels
Mockery\Exception\InvalidCountException: Method add() from Mockery_1_App_RFDevice should be called
exactly 1 times but called 0 times.
But the funny and perplexing thing is that if I comment out and combination of 2 of the 3 mockery sections, my test pass. This means to mean, that my code is actually working correctly, but for some reason in this case, I can't inject multiple mocked model objects into my controller and test them all at once. I guess I could split this up into three separate tests that make sure each model's add(...) function is called, but I want to do it all in one test case if possible. I also know I could use a repository pattern to wrap all the business logic in the controller's add(...) function into a single call, but then I would run into the same problem while testing the repository class.
You're not mocking the return values of the methods so this line attempts to access an attribute (id) on a null.
$newDeviceId = $this->deviceModel->add($name, $description, $type)->id;
You can fix this by adding a return value to your Device model mock like so:
$mockDeviceModel = Mockery::mock(Device::class);
$device = new Device;
$mockDeviceModel->shouldReceive('add')->withAnyArgs()->once()->andReturn($device);
To make such problems easier to debug in the future, change your error handler to re-throw the exceptions in a testing environment instead of rendering a valid HTML response.

Mocking a service called by a controller from a WebTestCase

I have an API written using Symfony2 that I'm trying to write post hoc tests for. One of the endpoints uses an email service to send a password reset email to the user. I'd like to mock out this service so that I can check that the right information is sent to the service, and also prevent an email from actually being sent.
Here's the route I'm trying to test:
/**
* #Route("/me/password/resets")
* #Method({"POST"})
*/
public function requestResetAction(Request $request)
{
$userRepository = $this->get('app.repository.user_repository');
$userPasswordResetRepository = $this->get('app.repository.user_password_reset_repository');
$emailService = $this->get('app.service.email_service');
$authenticationLimitsService = $this->get('app.service.authentication_limits_service');
$now = new \DateTime();
$requestParams = $this->getRequestParams($request);
if (empty($requestParams->username)) {
throw new BadRequestHttpException("username parameter is missing");
}
$user = $userRepository->findOneByUsername($requestParams->username);
if ($user) {
if ($authenticationLimitsService->isUserBanned($user, $now)) {
throw new BadRequestHttpException("User temporarily banned because of repeated authentication failures");
}
$userPasswordResetRepository->deleteAllForUser($user);
$reset = $userPasswordResetRepository->createForUser($user);
$userPasswordResetRepository->saveUserPasswordReset($reset);
$authenticationLimitsService->logUserAction($user, UserAuthenticationLog::ACTION_PASSWORD_RESET, $now);
$emailService->sendPasswordResetEmail($user, $reset);
}
// We return 201 Created for every request so that we don't accidently
// leak the existence of usernames
return $this->jsonResponse("Created", $code=201);
}
I then have an ApiTestCase class that extends the Symfony WebTestCase to provide helper methods. This class contains a setup method that tries to mock the email service:
class ApiTestCase extends WebTestCase {
public function setup() {
$this->client = static::createClient(array(
'environment' => 'test'
));
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
$this->mockEmailService = $mockEmailService;
}
And then in my actual test cases I'm trying to do something like this:
class CreatePasswordResetTest extends ApiTestCase {
public function testSendsEmail() {
$this->mockEmailService->expects($this->once())
->method('sendPasswordResetEmail');
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
}
}
So now the trick is to get the controller to use the mocked version of the email service. I have read about several different ways to achieve this, so far I've not had much luck.
Method 1: Use container->set()
See How to mock Symfony 2 service in a functional test?
In the setup() method tell the container what it should return when it's asked for the email service:
static::$kernel->getContainer()->set('app.service.email_service', $this->mockEmailService);
# or
$this->client->getContainer()->set('app.service.email_service', $this->mockEmailService);
This does not effect the controller at all. It still calls the original service. Some write ups I've seen mention that the mocked service is 'reset' after a single call. I'm not even seeing my first call mocked out so I'm not certain this issue is affecting me yet.
Is there another container I should be calling set on?
Or am I mocking out the service too late?
Method 2: AppTestKernel
See: http://blog.lyrixx.info/2013/04/12/symfony2-how-to-mock-services-during-functional-tests.html
See: Symfony2 phpunit functional test custom user authentication fails after redirect (session related)
This one pulls me out of my depth when it comes to PHP and Symfony2 stuff (I'm not really a PHP dev).
The goal seems to be to change some kind of foundation class of the website to allow my mock service to be injected very early in the request.
I have a new AppTestKernel:
<?php
// app/AppTestKernel.php
require_once __DIR__.'/AppKernel.php';
class AppTestKernel extends AppKernel
{
private $kernelModifier = null;
public function boot()
{
parent::boot();
if ($kernelModifier = $this->kernelModifier) {
$kernelModifier($this);
$this->kernelModifier = null;
};
}
public function setKernelModifier(\Closure $kernelModifier)
{
$this->kernelModifier = $kernelModifier;
// We force the kernel to shutdown to be sure the next request will boot it
$this->shutdown();
}
}
And a new method in my ApiTestCase:
// https://stackoverflow.com/a/19705215
protected static function getKernelClass(){
$dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
$finder = new Finder();
$finder->name('*TestKernel.php')->depth(0)->in($dir);
$results = iterator_to_array($finder);
if (!count($results)) {
throw new \RuntimeException('Either set KERNEL_DIR in your phpunit.xml according to http://symfony.com/doc/current/book/testing.html#your-first-functional-test or override the WebTestCase::createKernel() method.');
}
$file = current($results);
$class = $file->getBasename('.php');
require_once $file;
return $class;
}
Then I alter my setup() to use the kernel modifier:
public function setup() {
...
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
static::$kernel->setKernelModifier(function($kernel) use ($mockEmailService) {
$kernel->getContainer()->set('app.service.email_service', $mockEmailService);
});
$this->mockEmailService = $mockEmailService;
}
This works! However I now can't access the container in my other tests when I'm trying to do something like this:
$c = $this->client->getKernel()->getContainer();
$repo = $c->get('app.repository.user_password_reset_repository');
$resets = $repo->findByUser($user);
The getContainer() method returns null.
Should I be using the container differently?
Do I need to inject the container into the new kernel? It extends the original kernel so I don't really know why/how it's any different when it comes to the container stuff.
Method 3: Replace the service in config_test.yml
See: Symfony/PHPUnit mock services
This method requires that I write a new service class that overrides the email service. Writing a fixed mock class like this seems less useful than a regular dynamic mock. How can I test that certain methods have been called with certain parameters?
Method 4: Setup everything inside the test
Going on #Matteo's suggestion I wrote a test that did this:
public function testSendsEmail() {
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
$mockEmailService->expects($this->once())
->method('sendPasswordResetEmail');
static::$kernel->getContainer()->set('app.service.email_service', $mockEmailService);
$this->client->getContainer()->set('app.service.email_service', $mockEmailService);
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
}
This test fails because the expected method sendPasswordResetEmail wasn't called:
There was 1 failure:
1) Tests\Integration\Api\MePassword\CreatePasswordResetTest::testSendsEmail
Expectation failed for method name is equal to <string:sendPasswordResetEmail> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
Thanks to Cered's advice I've managed to get something working that can test that the emails I expect to be sent actually are. I haven't been able to actually get the mocking to work so I'm a bit reluctant to mark this as "the" answer.
Here's a test that checks that an email is sent:
public function testSendsEmail() {
$this->client->enableProfiler();
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
$mailCollector = $this->client->getProfile()->getCollector('swiftmailer');
$this->assertEquals(1, $mailCollector->getMessageCount());
$collectedMessages = $mailCollector->getMessages();
$message = $collectedMessages[0];
$this->assertInstanceOf('Swift_Message', $message);
$this->assertEquals('Reset your password', $message->getSubject());
$this->assertEquals('info#example.com', key($message->getFrom()));
$this->assertEquals($this->user->getEmail(), key($message->getTo()));
$this->assertContains(
'This link is valid for 24 hours only.',
$message->getBody()
);
$resets = $this->getResets($this->user);
$this->assertContains(
$resets[0]->getToken(),
$message->getBody()
);
}
It works by enabling the Symfony profiler and inspecting the swiftmailer service. It's documented here: http://symfony.com/doc/current/email/testing.html

ZF2 - Job queue to create a PDF file using SlmQueueBeanstalkd and DOMPDFModule

I'm trying to run a job queue to create a PDF file using SlmQueueBeanstalkd and DOMPDFModule in ZF".
Here's what I'm doing in my controller:
public function reporteAction()
{
$job = new TareaReporte();
$queueManager = $this->serviceLocator->get('SlmQueue\Queue\QueuePluginManager');
$queue = $queueManager->get('myQueue');
$queue->push($job);
...
}
This is the job:
namespace Application\Job;
use SlmQueue\Job\AbstractJob;
use SlmQueue\Queue\QueueAwareInterface;
use SlmQueue\Queue\QueueInterface;
use DOMPDFModule\View\Model\PdfModel;
class TareaReporte extends AbstractJob implements QueueAwareInterface
{
protected $queue;
public function getQueue()
{
return $this->queue;
}
public function setQueue(QueueInterface $queue)
{
$this->queue = $queue;
}
public function execute()
{
$sm = $this->getQueue()->getJobPluginManager()->getServiceLocator();
$empresaTable = $sm->get('Application\Model\EmpresaTable');
$registros = $empresaTable->listadoCompleto();
$model = new PdfModel(array('registros' => $registros));
$model->setOption('paperSize', 'letter');
$model->setOption('paperOrientation', 'portrait');
$model->setTemplate('empresa/reporte-pdf');
$output = $sm->get('viewPdfrenderer')->render($model);
$filename = "/path/to/pdf/file.pdf";
file_put_contents($filename, $output);
}
}
The first time you run it, the file is created and the work is successful, however, if you run a second time, the task is buried and the file is not created.
It seems that stays in an endless cycle when trying to render the model a second time.
I've had a similar issue and it turned out it was because of the way ZendPdf\PdfDocument reuses it's object factory. Are you using ZendPdf\PdfDocument?
You might need to correctly close factory.
class MyDocument extends PdfDocument
{
public function __destruct()
{
$this->_objFactory->close();
}
}
Try to add this or something similar to the PdfDocument class...
update : it seem you are not using PdfDocument, however I suspect this is the issue is the same. Are you able to regenerate a second PDF in a normal http request? It is your job to make sure the environment is equal on each run.
If you are unable to overcome this problem a short-term quick solution would be to set max_runs configuration for SlmQueue to 1. That way the worker is stopped after each job and this reset to a vanilla state...

getting started with mocking in PHP

How do I get started with mocking a web service in PHP? I'm currently directly querying the web API's in my unit testing class but it takes too long. Someone told me that you should just mock the service. But how do I go about that? I'm currently using PHPUnit.
What I have in mind is to simply save a static result (json or xml file) somewhere in the file system and write a class which reads from that file. Is that how mocking works? Can you point me out to resources which could help me with this. Is PHPUnit enough or do I need other tools? If PHPUnit is enough what part of PHPUnit do I need to check out? Thanks in advance!
You would mock the web service and then test what is returned. The hard coded data you are expecting back is correct, you set the Mock to return it, so then additional methods of your class may continue to work with the results. You may need Dependency Injection as well to help with the testing.
class WebService {
private $svc;
// Constructor Injection, pass the WebService object here
public function __construct($Service = NULL)
{
if(! is_null($Service) )
{
if($Service instanceof WebService)
{
$this->SetIWebService($Service);
}
}
}
function SetWebService(WebService $Service)
{
$this->svc = $Service
}
function DoWeb($Request)
{
$svc = $this->svc;
$Result = $svc->getResult($Request);
if ($Result->success == false)
$Result->Error = $this->GetErrorCode($Result->errorCode);
}
function GetErrorCode($errorCode) {
// do stuff
}
}
Test:
class WebServiceTest extends PHPUnit_Framework_TestCase
{
// Simple test for GetErrorCode to work Properly
public function testGetErrorCode()
{
$TestClass = new WebService();
$this->assertEquals('One', $TestClass->GetErrorCode(1));
$this->assertEquals('Two', $TestClass->GetErrorCode(2));
}
// Could also use dataProvider to send different returnValues, and then check with Asserts.
public function testDoWebSericeCall()
{
// Create a mock for the WebService class,
// only mock the getResult() method.
$MockService = $this->getMock('WebService', array('getResult'));
// Set up the expectation for the getResult() method
$MockService->expects($this->any())
->method('getResult')
->will($this->returnValue(1)); // Change returnValue to your hard coded results
// Create Test Object - Pass our Mock as the service
$TestClass = new WebService($MockService);
// Or
// $TestClass = new WebService();
// $TestClass->SetWebServices($MockService);
// Test DoWeb
$WebString = 'Some String since we did not specify it to the Mock'; // Could be checked with the Mock functions
$this->assertEquals('One', $TestClass->DoWeb($WebString));
}
}
This mock may then be used in the other functions since the return is hard coded, your normal code would process the results and perform what work the code should (Format for display, etc...). This could also then have tests written for it.

Categories