I have a small problem which I think is quite simple to solve for experienced PHPUnit users.
I'm working with ZF2.
I'm working with a web service that returns plain text(CSV). I'd like to unit test the service that I've created.
I currently have a working configuration which is not the right way to do it I think.. I'm using mocks now when I'm unit testing my models and I have seen that PHPUnit has a special mock for web services, but that only supports WSDL.
Beneath you'll find my code and I hope someone can help me out with some explanation about the best practice for this situation.
The docs and the topics out here did not help me out (yet).
Thanks in advance!
The test itself:
public function testCanSearchSteeringWheels()
{
// Create the entry and fill it with the data that should be retrieved from the web service
$steeringWheelEntity = new SteeringWheelEntity();
$steeringWheelEntity->setId('170633')
->setName('Nice steering wheel one')
->setGrossPrice(100)
->setNetPrice(75);
// Setup the http client which whill make the final call to the web service
$httpClient = new Client();
$httpClient->setOptions(array(
'maxredirects' => 5,
'timeout' => 60,
))
->setAuth($this->config['supplier_name']['api']['username'], $this->config['supplier_name']['api']['password'])
;
$steeringWheelService = new SteeringWheelService($httpClient, new Request(), $this->config['supplier_name']);
// Search for a steering wheel by id code
$searchResult = $steeringWheelService->search('ID=5221552658987');
$this->assertEquals($steeringWheelEntity, $searchResult[0]);
}
The SteeringWheelEntity
namespace SupplierName\Entity;
class SteeringWheelEntity
{
// vars
// exchange array method
// getters methods
// setters methods
}
The SteeringWheelService
namespace SupplierName\Service;
use SupplierName\Entity\SteeringWheelEntity;
class SteeringWheelService extends AbstractWebService
{
/**
* search()
*
* #param string $param
* #return array
*/
public function search($param)
{
$this->appendUrl('ww0800?3,' . $param);
$response = $this->dispatch();
$parsedBody = $this->parse($response->getBody());
$entities = array();
foreach ($parsedBody as $data)
{
$steeringWheel = new SteeringWheelEntity();
// Fill SteeringWheelEntity with data
$entities[] = $steeringWheel;
}
return $entities;
}
}
The AbstractWebService
use \Zend\Http\Client;
use \Zend\Http\Request;
class AbstractWebService
{
private $httpClient;
private $request;
private $response;
protected $config;
private $url;
public function __construct(Client $httpClient, Request $request, Array $config)
{
$this->url = $config['api']['url'];
$this->httpClient = $httpClient;
$this->request = $request;
$this->config = $config;
}
protected function setUrl($url)
{
$this->url = $url;
return $this->url;
}
protected function appendUrl($string)
{
$this->url .= $string;
}
protected function getUrl()
{
return $this->url;
}
public function dispatch()
{
$this->request->setUri($this->getUrl());
$this->response = $this->httpClient->dispatch($this->request);
if (!$this->response->isSuccess()) {
throw new \Exception('HTTP error #' . $this->response->getStatusCode() . ' when connecting to ' . $this->getUrl() . '.');
}
return $this->response;
}
public function parse()
{
// Parse the content
}
}
Rather than using a mock for a web service. Could you just mock the \Zend\Http\Request and \Zend\Http\Client objects as they are doing the work for you? This way you have control over what the Zend objects return to you versus having to try to mock the web service.
That would be how I would go about testing the services.
Related
I'm trying to write test code for monosilic code like below.
Q1. How to write test code which has access to DB?
Q2. How to refactor these code testable?
Q3. Is there any way to write test code with fewer change to production code?
I need your help!
Thanks!!
Example Production Code)
<?
class Sample_Model_Service_A
{
private $_result
private $_options
private $_someValue
public function __construct($params, $ids, $data) {
$this->_options = Sample_Model_Service_B::getOption($data);
}
private function setSomeValue() {
// some code shaping $_params to $someValue with $this->_options
$this->_someValue= $someValue;
}
// want to write test for this function
// changed this function's logic
private function setResult() {
// some code shaping $_someValue to $result
$this->_result = $result;
}
public function getter() {
retrn $this->_result;
}
}
?>
<?
class Sample_Model_Service_B
{
// get option from DB
public static function getOption($data) {
$dao = new Model_Dao_Option();
$option = $dao->getOption($data['id']);
return $option;
}
}
?>
My Test Code so far)
public function testsetResult()
{
// just make sure these variables are defined
$params = $ids = $data = [];
// try to make test for private function
$sample = new Sample_Model_Service_A($params, $ids, $data);
$reflection = new ReflectionClass($sample);
// get Method
$method = $reflection->getMethod('setresult');
$method->setAccessible(true);
// wondering how to get $result
$result = $method->invoke($sample);
// assert
$this->assertSame($result);
}
Mockery solved my issue.
Sample Code)
/**
* #dataProvider sampleProvider
*/
public function testsetResult($sampleData)
{
// mock Sample_Model_Service_B
$mockSample_Model_Service_B = Mockery::mock('alias:' . Sample_Model_Service_B::class);
$mockSample_Model_Service_B->shouldReceive('getOption')->andReturn($sampleData['option']);
$sample = new Sample_Model_Service_A($sampleData['params'], $sampleData['ids'], $sampleData['data']);
$sample->setResult();
$result = $sample->getter();
// assert
$this->assertSame($result, $sampleData['result']);
}
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.
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.
I'm trying to write white test to test my API with file uploads.
I'm following the docs about this using basic client request, not crawler.
The unit test is:
class RecordsControllerTest extends WebTestCase {
private $client;
public function __construct() {
parent::__construct();
$this->client = self::createClient();
$this->client->insulate();
}
public function testApiPostUpload($params){
$fileToUpload = realpath(__DIR__.'/../../resources/mpthreetest.mp3');
$file = new UploadedFile(
$fileToUpload,
'mpthreetest.mp3',
MimeTypeGuesser::getInstance()->guess($fileToUpload),
filesize($fileToUpload)
);
$this->client->request('POST', '/records/'.$params['createdRecordId'].'/upload', array(), array('file' => $file) );
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
}
When I execute the test I receive an error:
Exception: Serialization of 'Symfony\Component\HttpFoundation\File\UploadedFile' is not allowed
/path/to/project/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Client.php:165
/path/to/project/vendor/symfony/symfony/src/Symfony/Component/BrowserKit/Client.php:348
/path/to/project/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Client.php:143
/path/to/project/vendor/symfony/symfony/src/Symfony/Component/BrowserKit/Client.php:313
/path/to/project/src/Bundle/Tests/Functional/Controller/RecordsControllerTest.php:182
I have found this question for about the same error, but in this case the request is not sent to the controller and the problem is not the entity and implementing serialization.
Anyone who knows how to fix this?
Anyone who managed to make unit test for uploading file in symfony 2?
You could try to NOT insulate the requests passing false as argument to the insulate method so try this:
$this->client->insulate(false);
instead of this:
$this->client->insulate();
Hope this help
I was able to resolve it by setting the changeHistory parameter to false (7th and last parameter in the request method signature):
$crawler = $client->request($form->getMethod(), $form->getUri(), $values, $files, [], null, false);
This will prevent the serialize on following lines :
if ($this->followRedirects && $this->redirect) {
$this->redirects[serialize($this->history->current())] = true;
return $this->crawler = $this->followRedirect();
}
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);
}