PHPunit: testing a Repository - unit-testing

I am testing my repository in Laravel and I came across a few issues, most probably with regards to the structure of my methods.
So, my repository looks like:
<?php
namespace Repositories\User;
use App\Test\Models\Entities\User;
use Illuminate\Database\Eloquent\Model;
class UserRepository implements UserInterface
{
/**
* #var Model $userModel
*/
protected $userModel;
/**
* Setting our class $userModel to the injected model
*
* #param Model $userModel
* #return UserRepository
*/
public function __construct(Model $userModel)
{
$this->userModel = $userModel;
}
/**
* Returns the User object associated with the userEmail
*
* #param string $userEmail
* #return User | null
*/
public function getUserByEmail($userEmail)
{
// Search by email
$user = $this->userModel
->where('email', '=', strtolower($userEmail))
->first();
if ($user) {
return $user->first();
}
return null;
}
}
/**
* #param $id
* #param $email
* #param $source
*
* #dataProvider usersDataProvider
*/
public function testGetUserByEmail($id, $email, $source)
{
$user = new User();
$user->id = $id;
$user->email = $email;
$user->user_source_id = $source;
$this->user->shouldReceive('getUserByEmail')->once()
->andReturn($user);
}
I am quite new working with Mockery and am just wondering whether I am following the correct approach in order to test my getUserByEmail($email) method. Please bare in mind that (as expected) getUserByEmail($email) makes a call to the Database.
Also, this is the message that I receive:
PHP Fatal error: Call to a member function connection() on null in /private/var/www/ff-php-prelaunch/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php, which probably implies that there is no initialized connection to the DB.
UPDATE
Btw, my setUp() is as follows:
public function setUp()
{
$this->user = Mockery::mock('App\Test\Models\Entities\User');
parent::setUp();
}

You probably don't extend the default TestCase class provided by Laravel in the tests directory. Your environment is not correct and you have no connection to the database.
If you don't want to actually query the database you should use Mockery to create a mock of your dependencies (here $userModel). You basically create a Mock the following way, I didn't test this code but the general idea is here.
protected function setUp() {
parent::setUp();
$userModelMockedMethods = [
'where' => 'some return'
];
// This is our dependency mock
$userModelMock = Mockery::mock(Model::class, $userModelMockedMethods);
// this mock now will return 'some return' if you call the `where` method
// on it. If you wish the where method to return something callable, you
// should return another mock instead of a string
// This replaces the mock in the dependency manager.
$this->app->instance(Model::class, $UserModelMock);
}

Related

Symfony4 Doctrine Associations insert

I get xml data from external soap server, parsing data and create Object. Next I want to persist it in database but it does't work.
Company id, I get from external soap and its string unique value like 387sdfh899ohkadkfh8.
Company
/**
* #ORM\Entity
*/
class Company
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="NONE")
* #ORM\Column(type="string")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Address", mappedBy="company", orphanRemoval=true, cascade={"persist","remove"})
*/
private $addresses;
// ...
}
Address
/**
* #ORM\Entity
*/
class Address
{
// ...
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Company",inversedBy="adresses")
* #ORM\JoinColumn(nullable=false)
*/
private $company;
// ...
}
CompanyController
class CompanyController
{
// ...
$json = $serializer->serialize($data, 'json');
$obj = $serializer->deserialize($json, 'array<App\Entity\Company>', 'json');
// ...
}
Every thing looks as expected. Object was created including two Address objects.
Update - begin
This is structure what I get from deserialize
array:1 [
0 => Company {#524
-id: "0946346d06ffe3f551a80700c2a5c534"
// ..
-addresses: ArrayCollection {#538
-elements: array:2 [
0 => Address {#1017
-id: null
// ...
-company: null
}
1 => Address {#537
-id: null
// ..
-company: null
}
]
}
-status: "Active"
}
]
Update - end
But when I try to store it in database:
CompanyController
class CompanyController
{
// ...
$em= $this->getDoctrine()->getManager();
foreach ($obj as $o) $em->persist($o);
$em->flush();
// ...
}
I'm getting error
inserting address doesn't include id of company. company_id is setting to null
Similar json data, including addresses corresponding to company I'll be getting from client with json format, parsing it via FormType and store to database but I can't manage with :/
How should I insert that related objects in proper way?
Ok, I solved the problem
Company
/**
* #ORM\OneToMany(targetEntity="App\Entity\Address", mappedBy="company", orphanRemoval=true, cascade={"persist","remove"})
* #JMS\Accessor(setter="setAddresses")
*/
private $addresses;
And added method:
/**
* #param ArrayCollection $addresses
* #return Company
*/
public function setAddresses(ArrayCollection $addresses): self
{
if ( !$this->addresses instanceof ArrayCollection ){
$this->addresses = new ArrayCollection();
};
foreach ($addresses as $address){
if (!$this->addresses->contains($address)) {
$this->addresses[] = $address;
$address->setCompany($this);
}
}
return $this;
}
I spend on this issue two days :/ and solution was so easy, #malarzm Thanks for suggestion it helped me a lot.

Symfony 4 Mock service in functional test

I am testing a service which essentially is mostly serializing an object and sending it via a service to an external system.
If I create the typical unittest I would mock the response of the serializer and of the service, which contacts the external system. In fact there would be not much left to test except calling a bunch of setter Methods in my object.
The alternative would be using a KernelTestCase and creating a functional test, which would be fine except I don't want to contact the external system, but to use a mock only for this "external" service.
Is there any possibility to achieve this in Symfony 4?
Or is there another approach to this?
What I am doing now is the following:
<?php
namespace App\Tests\Service;
use App\Service\MyClassService;
use App\Service\ExternalClient\ExternalClient;
use JMS\Serializer\Serializer;
use JMS\Serializer\SerializerInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;
class MyClassServiceTest extends KernelTestCase
{
/** #var LoggerInterface */
private $logger;
/** #var Serializer */
private $serializer;
/** #var ExternalClient */
private $externalClient;
/** #var RequestInterface */
private $request;
/** #var MyClassService */
private $myClassService;
public function setUp()
{
$kernel = self::bootKernel();
$this->logger = $kernel->getContainer()->get(LoggerInterface::class);
$this->serializer = $kernel->getContainer()->get(SerializerInterface::class);
$this->externalClient = $this->createMock(ExternalClient::class);
}
public function testPassRegistrationData()
{
$getParams = [
'amount' => '21.56',
'product_id' => 867,
'order_id' => '47t34g',
'order_item_id' => 2,
'email' => 'kiki%40bubu.com',
];
$this->generateMyClassService($getParams);
$userInformation = $this->myClassService->passRegistrationData();
var_dump($userInformation);
}
/**
* generateMyClassService
*
* #param $getParams
*
* #return MyClass
*/
private function generateMyClassService($getParams)
{
$this->request = new Request($getParams, [], [], [], [], [], null);
$this->myClassService = new MyClassService(
$this->logger,
$this->serializer,
$this->externalClient,
$this->request
);
}
}
Give back this error:
Symfony\Component\DependencyInjection\Exception\RuntimeException: Cannot autowire service "App\Service\MyClassConfirmationService": argument "$request" of method "__construct()" references class "Symfony\Component\HttpFoundation\Request" but no such service exists.
You shouldn't inject Request into your services. You should use Symfony\Component\HttpFoundation\RequestStack instead of Request. Also, you should check if $requestStack->getCurrentRequest() doesn't return null. I suppose you get such error in process of container's initialization but you execute just a script (test) and of course, you don't have Request on it.
Since the whole thing needed some more tweak here I post my solution to which Nikita's answer lead me:
As he suggested I replaced "request in my service with RequestStack which worked out fine:
/**
* #param LoggerInterface $logger
* #param SerializerInterface $serializer
* #param ExternalClient $externalClient
* #param RequestStack $requestStack
*/
public function __construct(
LoggerInterface $logger,
SerializerInterface $serializer,
ExternalClient $externalClient,
RequestStack $requestStack
) {
$this->logger = $logger;
$this->serializer = $serializer;
$this->externalClient = $externalClient;
$this->request = $requestStack->getCurrentRequest();
$this->params = $this->request->query;
}
In my test I faked the request like this:
$this->request = new Request($getParams, [], [], [], [], [], null);
$this->requestStack = new RequestStack();
$this->requestStack->push($this->request);
However with that having fixed my next problems arised, since my class also asks for the logger and the serializer.
For the Logger I used a general Loggerclass I created especially for this test situations. But that leaves me to get the serializer and I wanted the real one or else I could have stuck to a mostly useless UnitTest.
That is what I did:
public function setUp()
{
$kernel = self::bootKernel();
$container = self::$container;
$this->serializer = $container->get('jms_serializer.serializer');
}
This then gave me the real serializer from the container.
Now I can let my mocked external client give me mocked answers and I can test my service for reaction without bothering an external service.

Laravel 5.5 Service Provider

I am trying to have a custom service provider with common methods that I am using throughout my application. However I am getting an error target not instantiable. I have the same working fine in Laravel 5.3 but now its not working in Laravel 5.5. Here is my code:
In the app\Helpers folder, I have created a folder Contracts with an interface FrontendContracts.
namespace App\Helpers\Contracts;
Interface FrontendContracts{
public function randomString($len);
}
In app\Helpers I have a class FrontendMethods which implements the interface
namespace App\Helpers;
use App\Helpers\Contracts\FrontendContracts;
class FrontendMethods implements FrontendContracts{
public function randomString($len){
$chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmonpqrstuvwxyz";
$result = "";
$charArray = str_split($chars);
for($i = 0; $i < $len; $i++){
$randItem = array_rand($charArray);
$result .= "".$charArray[$randItem];
}
$result .= time();
return $result;
}
}
In the app\Providers I have FrontendServiceProvider class with:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Helpers\FrontendMethods;
class FrontendServiceProvider extends ServiceProvider{
protected $defer = true;
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot(){
//
}
/**
* Register the application services.
*
* #return void
*/
public function register(){
$this->app->bind('App\Helpers\Contracts\FrontendContracts', function(){
return new FrontendMethods();
});
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides(){
return ['App\Helpers\Contracts\FrontendContracts'];
}
}
I have registered the provider in the providers array as:
App\Providers\FrontendServiceProvider::class,
I am getting the error message Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}
and Target [App\Helpers\Contracts\FrontendContracts] is not instantiable.
Can someone kindly point at to me what I am doing wrong?
I don't think you need the provides() method in your FrontendServiceProvider class. Try removing this.

Add Method not working in a many to many relationship

I have this many to many relationship between bus and driver .
This is the bus entity :
/**
* #var ArrayCollection<Driver> The driver of this bus.
* #ORM\ManyToMany(targetEntity="Driver", inversedBy="bus" , cascade={"persist"})
* #ORM\JoinTable(name="bus_driver")
* #ORM\JoinColumn(name="driver_id", referencedColumnName="id")
* */
private $driver;
public function __construct() {
$this->driver = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addDriver($driver) {
$this->driver[] = $driver;
return $this;
}
And this is the driver entity :
/**
* #var ArrayCollection<Bus> The buses of this driver
* #ORM\ManyToMany(targetEntity="Bus", mappedBy="driver")
* #ORM\JoinTable(name="bus_driver")
* #ORM\JoinColumn(name="bus_id", referencedColumnName="id")
*/
private $bus;
public function __construct() {
$this->bus = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addBus($bus) {
$this->bus[] = $bus;
$bus->addDriver($this);
return $this;
}
My problem is that when I add a bus with a driver the relation is persisted but not when I add a driver whih a bus . It works only from the bus side.
please, consider renaming $driver into $drivers, as there is multiple drivers (same for bus -> buses)
and then you should try that:
#ORM\ManyToMany(targetEntity="xxx", cascade={"persist"})
more details: http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/reference/working-with-associations.html#transitive-persistence-cascade-operations
Change these (add the null and call it 'drivers'):
use Doctrine\Common\Collections\ArrayCollection;
...
private $drivers = null;
public function __construct() {
$this->drivers = new ArrayCollection();
}
public function addDriver($driver) {
$this->drivers[] = $driver;
return $this;
}
Also, to resolve the problem from the Bus Entity side, you might (but I'm not sure) need this change:
public function addDriver($driver) {
$driver->addBus($this);
$this->drivers[] = $driver;
return $this;
}
Try it, since I have a similar scenario in a ManyToOne relation, and I'm wondering if the above change might work.
The only working scenario was when I had :
A setter for the drivers collection .
An addDriver Method .
A removeDriver Method.
If I remove one of the pervious , addDriver won't even trigger at all .

Testing not working CakePHP2.0

I have created the a test for a controller using Cake bake command.
Now, I want to test the function "index" of the controller and for it I do this:
public function testIndex() {
echo "printed";
$result = $this->testAction("/comments/1");
echo "not printed";
}
1 is the param, the id of the post where the comment is. Anyway, the controller works perfectly well, there's no problem with it.
As you can see, the test crashes after calling the testAction method. (it doesn't print the second echo)
I have seen that if the action called on the controller has any call to its model, testAction call won't work. But, if the action to test doesn't have any call to any Model, then, it works perfectly.
Whats happening here?
By the way, both databases, default and test has data in it so it's not either a problem with the database.
Thanks.
UPDATE:
here you have the rest of the testController generated by Cake bake command:
<?php
/* Comments Test cases generated on: 2012-04-12 11:49:17 : 1334224157*/
App::uses('CommentsController', 'Controller');
/**
* TestCommentsController *
*/
class TestCommentsController extends CommentsController {
/**
* Auto render
*
* #var boolean
*/
public $autoRender = false;
/**
* Redirect action
*
* #param mixed $url
* #param mixed $status
* #param boolean $exit
* #return void
*/
public function redirect($url, $status = null, $exit = true) {
$this->redirectUrl = $url;
}
}
/**
* CommentsController Test Case
*
*/
class CommentsControllerTestCase extends CakeTestCase {
/**
* Fixtures
*
* #var array
*/
public $fixtures = array('app.comment');
/**
* setUp method
*
* #return void
*/
public function setUp() {
parent::setUp();
$this->Comments = new TestCommentsController();
$this->Comments->constructClasses();
}
/**
* tearDown method
*
* #return void
*/
public function tearDown() {
unset($this->Comments);
parent::tearDown();
}
When you're testing controllers, make sure to extend the test case class by ControllerTestCase to take advantage of the testAction() method.