How do I test this class using phpunit? - php

I am using Laravel 4.2 and am trying to get into using phpunit to test my code rather than manually test everything. I have read Jeffrey Way's book 'Laravel Testing Decoded' but I still finding my very first test tricky. The class I am trying to test is below. What I am struggling with is - what should I test?
I don't think I should test the database or the Model $advert as these should have their own tests. In which case I think I need to either mock $advert or create a factory for it but I don't know which.
Any pointers would be greatly appreciated.
EloquentListing.php
<?php
namespace PlaneSaleing\Repo\Listing;
use Illuminate\Database\Eloquent\Model;
class EloquentListing implements ListingInterface {
protected $advert;
public function __construct(Model $advert)
{
$this->advert = $advert;
}
/**
* Get paginated listings
*
* #param int Current page
* #param int Number of listings per page
* #return StdClass object with $items and $totalItems for pagination
*/
public function byPage($page=1, $limit=10)
{
$result = new \StdClass;
$result->page = $page;
$result->limit = $limit;
$result->totalItems = 0;
$result->items = array();
$listings = $this->advert
->orderBy('created_at')
->skip( $limit * ($page-1) )
->take($limit)
->get();
// Create object to return data useful for pagination
$result->items = $listings->all();
$result->totalItems = $this->totalArticles;
return data;
}
/**
* Get total listing count
*
*
*/
protected function totalArticles()
{
return $this->advert->count();
}
}

You have to test every method you have in your class. You have constructor, which should be also tested, to see, if it sets model to your attributes, as well your protected method.
You should mock your model with mockery. It can be installed with
$ composer require mockery/mockery
Then in your test file:
<?php
use Mockery;
use ReflectionClass;
use PlaneSaleing\Repo\Listing\EloquentListing;
class EloquentListingTest extends \TestCase
{
/**
* Testing if __constructor is setting up property
*/
public function testModelSetsUp()
{
$mock = Mockery::mock(Illuminate\Database\Eloquent\Model::class);
$listing = new EloquentListing($mock);
$reflection = new ReflectionClass($listing);
// Making your attribute accessible
$property = $reflection->getProperty('advert');
$property->setAccessible(true);
$this->assertInstanceOf(Illuminate\Database\Eloquent\Model::class, $property);
}
/**
* Here you will check if your model is recieving calls
*/
public function testByPage()
{
$mock = Mockery::mock(Illuminate\Database\Eloquent\Model::class);
$mock->shouldReceive('orderBy')
->with('created_at')
->once()
->andReturn(Mockery::self())
->shouldReceive('skip')
->with(10)
->once()
->andReturn(Mockery::self())
->shouldReceive('take')
->with(10)
->andReturn(Mockery::self())
->shouldReceive('get')
->once()
->andReturn(Mockery::self())
->shouldReceive('all')
->once()
->andReturn(Mockery::self());
$listing = new EloquentListing($mock);
}
/**
* Here you will see, if your model is receiving call '->count()'
*/
public function testTotalArticles()
{
$mock = Mockery::mock(Illuminate\Database\Eloquent\Model::class);
$mock->shouldReceive('count')
->once()
->andReturn(Mockery::self());
$listing = new EloquentListing($mock);
// We will have to set method accesible
$reflection = new ReflectionClass($listing);
$method = $reflection->getMethod('totalArticles');
$method->setAccessible(true);
$listing->totalArticles();
}
}

Related

Test laravel job calls another class

I'm trying to test that my laravel 9 job calls another class. I've mocked the class it's expected to call but the test fails with a response that the expected method is not called.
I've tried debugging and can't track that the method appears to be called so something wrong in either the way I'm mocking or using the mock and my test expectations.
So, how do I test that a method within a laravel job calls the method in the class.
Here's the job code:
class SendFixtureReminderEmailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The number of times the job may be attempted.
*
* #var int
*/
public int $tries = 3;
/**
* The number of seconds the job can run before timing out.
*
* #var int
*/
public int $timeout = 240;
private League $league;
private Fixture $fixture;
private FixtureReminderEmail $fixtureReminderEmail;
/**
* Create a new job instance.
*/
public function __construct(League $league)
{
$this->league = $league;
$this->fixtureReminderEmail = app()->make(FixtureReminderEmail::class, ['league'=>$this->league]);
}
/**
* Get the tags that should be assigned to the job.
*
* #return array
*/
public function tags()
{
return ['fixtureReminderEmail'];
}
/**
* #return void
* #throws Exception
*/
public function handle(): void
{
$counter = $this->fixtureReminderEmail->handle();
Log::info($counter . " Fixture " . Str::plural('reminder', $counter) . "sent");
}
}
and here's the latest version of my test:
class SendFixtureReminderJobTest extends TestCase
{
use RefreshDatabase;
use WithFaker;
private $adminUser;
protected function setUp(): void
{
parent::setUp();
}
/**
* #test
* #covers SendEclecticUpdateEmailJob::handle
* #description:
*/
public function testHandle()
{
Queue::fake();
$league = League::factory()->matchplay()->create();
$this->mock(FixtureReminderEmail::class, function($mock) use ($league) {
$mock->shouldReceive('handle')
->with($league)
->once()
->andReturn(6);
});
$this->withoutExceptionHandling();
$job = new SendFixtureReminderEmailJob($league);
$job->dispatch($league);
}
}
Any help in developing a test that the job calls the class would be great :)
Update:
I'm adding the variations i've recently tried to test that the job calls the method on the mock based on the help within comments:
with DispatchSyn
to avoid the queue and removed queue::fake
public function testHandle()
{
// Queue::fake();
$league = League::factory()->matchplay()->create();
$this->mock(FixtureReminderEmail::class, function($mock) use ($league) {
$mock->shouldReceive('handle')
->with($league)
->once()
->andReturn(6);
});
$this->withoutExceptionHandling();
$job = new SendFixtureReminderEmailJob($league);
$job->dispatchSync($league);
// Queue::assertPushed(SendFixtureReminderEmailJob::class, 1);
}
and this version by calling the jobs handle:
public function testHandle()
{
// Queue::fake();
$league = League::factory()->matchplay()->create();
$this->mock(FixtureReminderEmail::class, function($mock) use ($league) {
$mock->shouldReceive('handle')
->with($league)
->once()
->andReturn(6);
});
$this->withoutExceptionHandling();
$job = new SendFixtureReminderEmailJob($league);
$job->handle($league);
// Queue::assertPushed(SendFixtureReminderEmailJob::class, 1);
}
}
Resolved.
I carried on tinkering and creating the FixtureReminderEmail class within the test, bypassing the job to see what happened and it still failed.
I'm now going to kick myself as it was failing because of the way I was instantiating the class. I was using new to create the new object whereas to enable Laravel to swap out the class with the mock object, in the Job class I should have used:
$reminder = app()->make(FixtureReminderEmail::class);
$reminder->handle($league);
It failed the first time when I tried using dispatchNow() with a reflection error but as #adamGriffin comment I don't want to test the framework, just my class does what it's meant to and reverted to calling the handle method on the job :)

CoR pattern appropriate dependency Injection with Symfony

I was working on a suitable example of the CoR(Chain-of-responsibility) pattern. I learned that it is often used as middleware layer in MVC.
I am trying to write a middleware layer suitable for the pattern.
I have set my scenario regarding the implementation of the pattern as follows: The classes defined in the controller method as Annotations are linked to each other with chains, and each class performs a task and moves on to the next task. CoR(Chain-of-responsibility) pattern implementation known so far.
When I look at the examples, none of the classes used as middleware have been injected with Depency injection.
My scenario is as follows: An "#Middleware()" annonation is defined to the Controller method. Sample
/**
* #Route("/", name="test")
* #Middleware({"App\Middleware\MiddlewareOne","App\Middleware\MiddlewareTwo"})
*/
public function test()
Then I get annotations in the "onKernelController" method with ControllerListener and create the classes defined here with a foreach loop and connect them to each other.
Below you will see the sample code blocks, respectively.
The problem I'm having here is How can I give the ContainerInterface or a Service class that I created in the constructor of the MiddlewareOne class to the constructor of this object?(I do not know which services are coming, a fixed service is not coming)
class ControllerListener
{
/**
* #param \Symfony\Component\HttpKernel\Event\ControllerEvent $controllerEvent
*/
public function onKernelController(ControllerEvent $controllerEvent)
{
MiddlewareManager::handle($controllerEvent);
}
}
class MiddlewareManager
{
public static function handle(ControllerEvent $controllerEvent)
{
$containerBuilder = new ContainerBuilder();
if (!is_array($controllers = $controllerEvent->getController())) {
return;
}
$reader = new AnnotationReader();
$request = $controllerEvent->getRequest();
$content = $request->getContent();
[ $controller, $methodName ] = $controllers;
$reflectionClass = new \ReflectionClass($controller);
$classAnnotation = $reader
->getClassAnnotation($reflectionClass, Middleware::class);
$reflectionObject = new \ReflectionObject($controller);
$reflectionMethod = $reflectionObject->getMethod($methodName);
$methodAnnotation = $reader
->getMethodAnnotation($reflectionMethod, Middleware::class);
if (!($classAnnotation || $methodAnnotation)) {
return;
}
$middlewareList = $classAnnotation->getMiddleware()['value'];
$middleware = null;
$temp = null;
foreach ($middlewareList as $class){
if ($middleware === null){
$middleware = new $class();
$temp = $middleware;
continue;
}
$class = new $class();
$temp->next($class);
$temp = $class;
}
return $middleware;
}
}
class DumpOneMiddleware implements Middleware
{
/** #var \App\Middleware\Middleware */
private $next;
/** #var \App\Service\ExampleService */
private $exampleService;
public function __construct(ExampleService $exampleService)
{
$this->exampleService = $exampleService;
}
/**
* #param \App\Middleware\Middleware $next
* #return \App\Middleware\Middleware
*/
public function next(Middleware $next)
{
$this->next = $next;
return $next;
}
/**
* #param \Symfony\Component\HttpFoundation\Request $request
*/
public function handle(Request $request)
{
if (!$this->next) {
return true;
}
return $this->next->handle($request);
}
}
Here I can export Container object via ControllerListener (to all middleware classes). But if I want to add another service, how can I do it?
When I examined these and similar topics, I came to the conclusion that I should look at the strategy pattern and Compailer Pass issues. But these topics didn't help me much or I couldn't understand.
I would be very grateful if you could indicate the deficiencies or mistakes in the scenario I applied and show with an example how I can inject Dependecy into the objects I created dynamically.

Php Mockery - How To Mock Correctly

I am trying to add some existing test cases to a pre existing project
Here is the API class
<?php
namespace MyApp\Api;
use MyApp\ApiBase;
use MyApp\Ethereum as Eth;
use PHPUnit\Runner\Exception;
/**
* Class Ethereum
* #package MyApp\Api
*/
class Ethereum extends ApiBase {
/**
* Function to get balances
* #param array $addresses
* #param string $tag
* #return array
*/
public function getBalances(array $addresses, string $tag = 'latest') {
$data = [];
foreach ($addresses as $addr) {
// validate address
if (!Eth::validateAddress($addr)) {
continue;
}
}
return $data;
}
}
The service class
<?php
namespace MyApp;
use MyApp\Ethereum\GethIpc;
use MyApp\Ethereum\GethWebsocket;
use PHPUnit\Runner\Exception;
/**
* Class Ethereum
* #package MyApp
*/
class Ethereum {
public static $subscriptions = [];
/**
* Ethereum constructor.
*/
public function __construct() {
$this->connection = new GethWebsocket();
$connect = $this->connection->connect();
}
/**
* Function to validate an address
* #param string $address
* #return bool
*/
public static function validateAddress(string $address) {
return preg_match("/^(0x)?[0-9a-fA-F]{40}$/", $address) !== 0;
}
}
My test class
<?php
declare(strict_types=1);
namespace MyApp\Test;
use MyApp\Api\Ethereum;
use MyApp\Ethereum as Eth;
use PHPUnit\Framework\TestCase;
use PHPUnit\Runner\Exception;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
/**
* #covers MyApp\Api\Ethereum
*/
final class EthereumTest extends MockeryTestCase {
protected $ethereumApi;
protected $ethereum_address;
//Setup method called before every method
protected function setUp(): void {
$this->ethereumApi = new Ethereum();
$this->ethereum_address = '0x0000000000000000000000000000000' . rand(100000000, 999999999);
//Mockery::globalHelpers();
//$mock = mock(MyApp\Ethereum::class);
}
public function testGetBalances_ValidEthereumAddress(): void {
$mockEthereumService = Mockery::mock("Eth");
$mockEthereumService->shouldReceive('validateAddress')->once()->with($this->ethereum_address)->andReturn(true);
//$mockEthereumService->shouldReceive('msg')->once()->with($this->ethereum_address)->andReturn(true);
$addresses = [$this->ethereum_address];
$result = $this->ethereumApi->getBalances($addresses);
$this->assertNotEmpty($result);
}
public function tearDown()
{
Mockery::close();
}
}
Everytime I run the test class - the mock is not working and the actual service class method is being called
Can anyone offer assistance on how I should get this mock example working correctly?
Not having experience with Mockery myself, but after looking into the documentation
http://docs.mockery.io/en/latest/reference/creating_test_doubles.html#overloading
http://docs.mockery.io/en/latest/cookbook/mocking_hard_dependencies.html
I'd assume you need to use the "overload" prefix and the full classname and remove the use-statement for MyApp\Ethereum from your test case.

method does not exist on this mock object - Laravel , Mockery

i'm trying to test a simple class. I'm following this tutorial( http://code.tutsplus.com/tutorials/testing-laravel-controllers--net-31456 ).
I have this error, while running tests:
Method Mockery_0_App_Interfaces_MealTypeRepositoryInterface::getValidator() does not exist on this mock object
Im using repository structure. So, my controller calls repository and that returns Eloquent's response.
I'm relatively new in php and laravel. And I've started learning to test a few days ago, so I'm sorry for that messy code.
My test case:
class MealTypeControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
$this->mock = Mockery::mock('App\Interfaces\MealTypeRepositoryInterface');
$this->app->instance('App\Interfaces\MealTypeRepositoryInterface' , $this->mock);
}
public function tearDown()
{
Mockery::close();
}
public function testIndex()
{
$this->mock
->shouldReceive('all')
->once()
->andReturn(['mealTypes' => (object)['id' => 1 , 'name' => 'jidlo']]);
$this->call('GET' , 'mealType');
$this->assertViewHas('mealTypes');
}
public function testStoreFails()
{
$input = ['name' => 'x'];
$this->mock
->shouldReceive('getValidator')
->once()
->andReturn(Mockery::mock(['fails' => true]));
$this->mock
->shouldReceive('create')
->once()
->with($input);
$this->call('POST' , 'mealType' , $input ); // this line throws the error
$this->assertRedirectedToRoute('mealType.create');//->withErrors();
$this->assertSessionHasErrors('name');
}
}
My EloquentMealTypeRepository:
Nothing really interesting.
class EloquentMealTypeRepository implements MealTypeRepositoryInterface
{
public function all()
{
return MealType::all();
}
public function find($id)
{
return MealType::find($id);
}
public function create($input)
{
return MealType::create($input);
}
public function getValidator($input)
{
return MealType::getValidator($input);
}
}
My eloquent implementation:
Nothing really interresting,too.
class MealType extends Model
{
private $validator;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'meal_types';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['name'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = [];
public function meals()
{
return $this->hasMany('Meal');
}
public static function getValidator($fields)
{
return Validator::make($fields, ['name' => 'required|min:3'] );
}
}
My MealTypeRepositoryInterface:
interface MealTypeRepositoryInterface
{
public function all();
public function find($id);
public function create($input);
public function getValidator($input);
}
And finally, My controller:
class MealTypeController extends Controller {
protected $mealType;
public function __construct(MealType $mealType)
{
$this->mealType = $mealType;
}
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
$mealTypes = $this->mealType->all();
return View::make('mealTypes.index')->with('mealTypes' ,$mealTypes);
}
/**
* Show the form for creating a new resource.
*
* #return Response
*/
public function create()
{
$mealType = new MealTypeEloquent;
$action = 'MealTypeController#store';
$method = 'POST';
return View::make('mealTypes.create_edit', compact('mealType' , 'action' , 'method') );
}
/**
* Validator does not work properly in tests.
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$input = ['name' => $request->input('name')];
$mealType = new $this->mealType;
$v = $mealType->getValidator($input);
if( $v->passes() )
{
$this->mealType->create($input);
return Redirect::to('mealType');
}
else
{
$this->errors = $v;
return Redirect::to('mealType/create')->withErrors($v);
}
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
*/
public function show($id)
{
return View::make('mealTypes.show' , ['mealType' => $this->mealType->find($id)]);
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return Response
*/
public function edit($id)
{
$mealType = $this->mealType->find($id);
$action = 'MealTypeController#update';
$method = 'PATCH';
return View::make('mealTypes.create_edit')->with(compact('mealType' , 'action' , 'method'));
}
/**
* Update the specified resource in storage.
*
* #param int $id
* #return Response
*/
public function update($id)
{
$mealType = $this->mealType->find($id);
$mealType->name = \Input::get('name');
$mealType->save();
return redirect('mealType');
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return Response
*/
public function destroy($id)
{
$this->mealType->find($id)->delete();
return redirect('mealType');
}
}
That should be everything. It's worth to say that the application works, just tests are screwed up.
Does anybody know, why is that happening? I cant see a difference between methods of TestCase - testIndex and testStoreFails, why method "all" is found and "getValidator" is not.
I will be thankful for any tips of advices.
Perhaps an aside, but directly relevant to anyone finding this question by its title:
If:
You are getting the error BadMethodCallException: Method Mockery_0_MyClass::myMethod() does not exist on this mock object, and
none of your mocks are picking up any of your subject's methods, and
your classes are being autoloaded, (e.g. using composer)
then before making your mock object, you need to force the loading of that subject, by using this line of code:
spl_autoload_call('MyNamespace\MyClass');
Then you can mock it:
$mock = \Mockery::mock('MyNamespace\MyClass');
In my PHPUnit tests, I often put that first line into the setUpBeforeClass() static function, so it only gets called once and is isolated from tests being added/deleted. So the Test class looks like this:
class MyClassTest extends PHPUnit_Framework_TestCase {
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
spl_autoload_call('Jodes\MyClass');
}
public function testIt(){
$mock = \Mockery::mock('Jodes\MyClass');
}
}
I have forgotten about this three times now, each time spending an hour or two wondering what on earth the problem was!
I have found a source of this bug in controller.
calling wrong
$v = $mealType->getValidator($input);
instead of right
$v = $this->mealType->getValidator($input);

How can I pass extra parameters to the routeMatch object?

I'm trying to unit test a controller, but can't figure out how to pass some extra parameters to the routeMatch object.
I followed the posts from tomoram at http://devblog.x2k.co.uk/unit-testing-a-zend-framework-2-controller/ and http://devblog.x2k.co.uk/getting-the-servicemanager-into-the-test-environment-and-dependency-injection/, but when I try to dispatch a request to /album/edit/1, for instance, it throws the following exception:
Zend\Mvc\Exception\DomainException: Url plugin requires that controller event compose a router; none found
Here is my PHPUnit Bootstrap:
class Bootstrap
{
static $serviceManager;
static $di;
static public function go()
{
include 'init_autoloader.php';
$config = include 'config/application.config.php';
// append some testing configuration
$config['module_listener_options']['config_static_paths'] = array(getcwd() . '/config/test.config.php');
// append some module-specific testing configuration
if (file_exists(__DIR__ . '/config/test.config.php')) {
$moduleConfig = include __DIR__ . '/config/test.config.php';
array_unshift($config['module_listener_options']['config_static_paths'], $moduleConfig);
}
$serviceManager = Application::init($config)->getServiceManager();
self::$serviceManager = $serviceManager;
// Setup Di
$di = new Di();
$di->instanceManager()->addTypePreference('Zend\ServiceManager\ServiceLocatorInterface', 'Zend\ServiceManager\ServiceManager');
$di->instanceManager()->addTypePreference('Zend\EventManager\EventManagerInterface', 'Zend\EventManager\EventManager');
$di->instanceManager()->addTypePreference('Zend\EventManager\SharedEventManagerInterface', 'Zend\EventManager\SharedEventManager');
self::$di = $di;
}
static public function getServiceManager()
{
return self::$serviceManager;
}
static public function getDi()
{
return self::$di;
}
}
Bootstrap::go();
Basically, we are creating a Zend\Mvc\Application environment.
My PHPUnit_Framework_TestCase is enclosed in a custom class, which goes like this:
abstract class ControllerTestCase extends TestCase
{
/**
* The ActionController we are testing
*
* #var Zend\Mvc\Controller\AbstractActionController
*/
protected $controller;
/**
* A request object
*
* #var Zend\Http\Request
*/
protected $request;
/**
* A response object
*
* #var Zend\Http\Response
*/
protected $response;
/**
* The matched route for the controller
*
* #var Zend\Mvc\Router\RouteMatch
*/
protected $routeMatch;
/**
* An MVC event to be assigned to the controller
*
* #var Zend\Mvc\MvcEvent
*/
protected $event;
/**
* The Controller fully qualified domain name, so each ControllerTestCase can create an instance
* of the tested controller
*
* #var string
*/
protected $controllerFQDN;
/**
* The route to the controller, as defined in the configuration files
*
* #var string
*/
protected $controllerRoute;
public function setup()
{
parent::setup();
$di = \Bootstrap::getDi();
// Create a Controller and set some properties
$this->controller = $di->newInstance($this->controllerFQDN);
$this->request = new Request();
$this->routeMatch = new RouteMatch(array('controller' => $this->controllerRoute));
$this->event = new MvcEvent();
$this->event->setRouteMatch($this->routeMatch);
$this->controller->setEvent($this->event);
$this->controller->setServiceLocator(\Bootstrap::getServiceManager());
}
public function tearDown()
{
parent::tearDown();
unset($this->controller);
unset($this->request);
unset($this->routeMatch);
unset($this->event);
}
}
And we create a Controller instance and a Request with a RouteMatch.
The code for the test:
public function testEditActionWithGetRequest()
{
// Dispatch the edit action
$this->routeMatch->setParam('action', 'edit');
$this->routeMatch->setParam('id', $album->id);
$result = $this->controller->dispatch($this->request, $this->response);
// rest of the code isn't executed
}
I'm not sure what I'm missing here. Can it be any configuration for the testing bootstrap? Or should I pass the parameters in some other way? Or am I forgetting to instantiate something?
What I did to solve this problem was move the Application::init() and the configuration from the Bootstrap to the setUp() method. It takes a while to load, but it works.
My Bootstrap has only the code needed to configure the autoloader, while the setUp() method has something similar to the old Bootstrap::go() code.

Categories