Laravel test class property not persisting - php

I'm trying to set a sensorId property that will be used by every method in my feature test. The problem is that it is not persisting between tests.
Here is my test class:
class LogApiPostTest extends TestCase
{
public $sensorId;
public function setUp(): void
{
parent::setUp();
$this->sensorId = 'SEN' . rand(10000000, 999999999);
}
public function test_creates_sensor()
{
$response = $this->post('/api/logs', $this->data([
//
]));
$response->assertStatus(200);
$response->assertSeeText('Sensor created');
}
public function test_updates_sensor()
{
$response = $this->post('/api/logs', $this->data([
//
]));
$response->assertStatus(200);
$response->assertSeeText('Sensor updated');
}
public function test_creates_car()
{
$response = $this->post('/api/logs', $this->data([
'CarID' => 'B',
]));
$response->assertStatus(200);
$response->assertSeeText('Car created');
}
public function test_updates_car()
{
$response = $this->post('/api/logs', $this->data([
'CarID' => 'B',
]));
$response->assertStatus(200);
$response->assertSeeText('Car updated');
}
public function test_is_unauthorized()
{
$response = $this->post('/api/logs', $this->data([
'token_id' => 'thisShouldntWork',
]));
$response->assertUnauthorized();
}
public function data($merge = [])
{
return array_merge([
'token_id' => config('api.token'),
'sensorID' => $this->sensorId,
'CarID' => 'A',
'LogType' => 'CarLog',
'Time' => time(),
'DrctnFlr' => '-02',
'MCSS' => 'STB',
'OpMode' => 'INA',
'DoorStats' => '][**',
'DoorCmd' => 'C6:C1>',
'OCSS' => 'GTN02',
'Load' => '002%',
], $merge);
}
}
I just want the sensorId property to persist for all tests in this class.

When there is a dependency between tests, you must use the #depends annotation.
This allows to ensure the execution order of tests is correct and also to pass values between tests. Instead of making sensorId to persist, just pass it from test to test.
https://phpunit.readthedocs.io/en/9.5/writing-tests-for-phpunit.html#test-dependencies

Related

What is the best design pattern to consume REST API

I want to consume a Rest API in Laravel (an MVC framework) but I resort to use __call and was wonder if there is a better design pattern for this.
I know this a bad choice and I'm looking for an alternative pattern but here is my Repository class:
namespace App\Repositories;
use App\Models\OnlinePayment;
use App\Models\Order;
use App\Models\Transaction;
use App\Models\User;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use SoapClient;
class Bank
{
protected $http;
protected $user;
public function __construct()
{
$this->http = new Client;
}
protected function index()
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/report';
$data = [
'user_gender' => $this->user->gender ?? 1,
'user_name' => $this->user->name,
'user_family' => $this->user->family ?? 'خالی',
'user_mobile' => $this->user->mobile,
'user_type' => $this->user->type->name,
];
$options = $this->options($data);
$res = $this->http->request('GET', $url, $options);
$response = json_decode($res->getBody(), true);
return $response;
}
protected function indexData($request)
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers';
$options = $this->options($request->all());
$res = $this->http->request('GET', $url, $options);
$response = response()->json(json_decode($res->getBody(), true), $res->getStatusCode());
return $response;
}
protected function show($national_id)
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers/' . $national_id;
$options = $this->options([]);
$res = $this->http->request('GET', $url, $options);
if ($res->getStatusCode() == 404) {
abort(404);
}
$response = json_decode($res->getBody(), true);
return $response;
}
protected function store($request)
{
$http = new Client;
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers';
$this->user = auth()->user();
$data = array_merge(
[
'customer_national_id' => $request->national_id,
'customer_gender' => $request->gender,
'customer_name' => $request->name,
'customer_family' => $request->family,
'customer_phone' => $request->phone,
'customer_mobile' => $request->mobile,
'customer_city_id' => $request->city_id,
], [
'user_name' => $this->user->nanfig() is a hidden dependency. The settings should also be passed via the construcme,
'user_family' => $this->user->family ?? 'خالی',
'user_mobile' => $this->user->mobile,
'user_type' => $this->user->type->name,
'user_gender' => $this->user->gender ?? 1,
]
);
$res = $http->request('POST', $url, [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . config('Bank.token'),
],
'json' => $data,
'http_errors' => false
]);
if (! in_array($res->getStatusCode(), [200, 422])) {
$error = ValidationException::withMessages([
'name' => 'خطای ' . $res->getStatusCode() . ' در تعویض کالا'
]);
throw $error;
}
$response = response()->json(json_decode($res->getBody(), true), $res->getStatusCode());
return $response;
}
protected function options($data)
{
$options = [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . config('Bank.token'),
],
'json' => $data,
'http_errors' => false
];
return $options;
}
public function __call($method, $arguments) {
if (method_exists($this, $method)) {
if (! isset($arguments[0]) || ! $arguments[0] instanceof User) {
$this->user = auth()->user();
} else {
$this->user = $arguments[0];
unset($arguments[0]);
}
return call_user_func_array(array($this, $method), $arguments);
}
}
}
then create an instance of it in controller constructor:
public function __construct()
{
$this->Bank = new Bank();
}
and use it in controller like this:
$response = $this->Bank->indexData($user, $request);
or this:
$response = $this->Bank->indexData($request);
I think the shown class is not a Repository class because a Repository is only responsible for reading and writing the date from a data source. Your class does too much and violates all basic MVC principles.
Some thinks I would fix:
A repository is not responsible for creating the response view data (like JSON)
A repository is not responsible for creating a response object
A repository is independent of the request/response
The method name index makes no sense because a repository is not a Controller action. Don't mix the model layer with the controller layer.
config() is a hidden dependency. The settings should also be passed via the constructor.
Instead use better separation:
Create a class BankApiClient
Dont use magic methods like __call
Instead use public methods like: getUserByNationalId(int $nationalId): UserData
and so on...
Let the controller action create / render the json response with the results of the BankApiClient.
__call is an magic method of php which allow to execute protected method outside of the object instance, this is a rupture of the class visibility.
If you want to call a method from outside it must be public
public function __construct()
{
$this->bank = new Bank()
}
Use Auto injection of the dependency
public function __construct(Bank $bank)
{
$this->bank = $bank;
}

PHP unit failure testing

I have to test this class for failure condition
<?php
namespace Gpx\Handlers;
use Gpx\EntityInfrastructure\Model\Events\PlatformSessionInitiated;
use Gpx\EntityInfrastructure\Model\Payload;
use Gpx\Hfx\Framework\MessageTransportApplication\Handler\SynchronousHandlerInterface;
use Gpx\Hfx\Framework\MessageTransportApplication\Handler\MessageHandlingContextInterface;
use Gpx\HfxEventSourcing\HfxAggregateRoot;
use Gpx\HfxEventSourcing\HfxEventMetadata;
use Gpx\HfxEventSourcing\HfxProjectionHelper;
use Gpx\HfxEventSourcing\HfxRepository;
use Gpx\Hfx\MessageTransport\Response\SendableResponse;
use Gpx\Exceptions\IdNotDefinedException;
use Gpx\Exceptions\AggregateNotFoundException;
class BroadcastPlatformSessionInitiated implements SynchronousHandlerInterface
{
/** #var HfxRepository */
private $repository;
/** #var HfxProjectionHelper */
private $projectionHelper;
public function __construct(HfxRepository $repository, HfxProjectionHelper $projectionHelper)
{
$this->repository = $repository;
$this->projectionHelper = $projectionHelper;
}
/*
* #param MessageHandlingContextInterface $context
* #return SendableResponse
*/
public function handleSynchronousMessage(MessageHandlingContextInterface $context): SendableResponse
{
try {
$content = $context->message();
$header = $context->rawMessage()->header();
$metadata = HfxEventMetadata::fromHfxHeader($header);
$payload = Payload::fromMessageContent($content);
$roleAggregate = HfxAggregateRoot::createEntityFromEvent(PlatformSessionInitiated::class, $payload, $metadata);
$this->repository->save($roleAggregate);
$currentEvent = $roleAggregate->currentEvent();
$context->sendNonBlockingAsynchronous('platform_session_initiated', $currentEvent);
$this->projectionHelper->updateReadModel(HfxAggregateRoot::class);
return SendableResponse::answerTo($context->rawMessage(), 1000, [
'responseMessage' => 'Success',
'event' => $currentEvent
]);
} catch (IdNotDefinedException $e) {
return SendableResponse::answerTo($context->rawMessage(), 2000, [
'responseMessage' => 'Failure. Session Id is not defined.'
]);
}
}
}
Following is the test case I have written
<?php
namespace Gpx\Tests\Handlers;
use Ramsey\Uuid\Uuid;
use Gpx\Json\JsonEncode;
use Prophecy\Argument;
use PHPUnit\Framework\TestCase;
use Gpx\HfxEventSourcing\HfxProjectionHelper;
use Gpx\HfxEventSourcing\HfxRepository;
use Gpx\Hfx\Framework\MessageTransportApplication\Handler\MessageHandlingContextInterface;
use Gpx\Handlers\BroadcastPlatformSessionInitiated;
use Gpx\Hfx\MessageTransport\Message\ReceivedMessage;
use Gpx\Exceptions\IdNotDefinedException;
class BroadcastPlatformSessionInitiatedTest extends TestCase
{
/** #var HfxRepository */
private $repository;
/** #var HfxProjectionHelper */
private $projectionHelper;
/** #var MessageHandlingContext */
private $context;
/**
* This will run before each test
*/
public function setUp()
{
// Expected return value of message() function of $this->context
$expectedReturnValue = [
"session_id" => "1a92-4376-a8eb-deaf208ssess11",
"user_id" => "we",
"access_jwt" => "C",
"access_token" => "john#gmail.com",
"refresh_token" => "C",
"refresh_token_expires" => "john#gmail.com"
];
// Expected return value of rawMessage() function of $this->context
$headerResponseExpected = [
'header' => [
'version' => '2.0',
'originId' => (string)Uuid::uuid4(),
'destination' => 'application/meta#1.0.0',
'sent' => '2017-12-19T10:12:37.941+00:00'
],
'content' => [
'session_id' => null,
'title' => "A task's title."
]
];
// Prediction of $this->context object starts
$this->context = $this->prophesize(MessageHandlingContextInterface::class);
$this->context->message(Argument::any())->willReturn($expectedReturnValue);
$encodedMessage = new JsonEncode($headerResponseExpected);
$rawMessage = ReceivedMessage::fromEncodedMessage($encodedMessage->asString());
$this->context->rawMessage()->willReturn($rawMessage);
$this->context->sendNonBlockingAsynchronous('platform_session_initiated', Argument::type("array"))
->shouldBeCalled();
// Prediction of $this->context object ends
}
// We have to test handleSynchronousMessage handler whether it is returning sendable response with certain properties in it.
public function testHandleSynchronousMessageForSuccess()
{
// Prophecy means prediction of the future object
$this->ravenRepository = $this->prophesize(HfxRepository::class);
$this->ravenRepository->save(Argument::any())
->shouldBeCalled();
// Mocking HfxProjectionHelper and calling the method updateReadModel which will return the string UpdateReadModel
$this->projectionHelper = $this->createMock(HfxProjectionHelper::class);
$this->projectionHelper->method('updateReadModel')
->willReturn('UpdateReadModel');
// Actual calling
$broadcastPlatformSessionInitiated = new BroadcastPlatformSessionInitiated($this->ravenRepository->reveal(), $this->projectionHelper);
$response = $broadcastPlatformSessionInitiated->handleSynchronousMessage($this->context->reveal());
$this->assertInstanceOf('Gpx\Hfx\MessageTransport\Response\SendableResponse', $response);
$this->assertArrayHasKey("responseMessage", $response->content()->data());
$this->assertArrayHasKey("event", $response->content()->data());
$this->assertEquals("Success", $response->content()->data()['responseMessage']);
}
// We have to test handleSynchronousMessage handler whether it is returning sendable response with certain properties in it.
public function testHandleSynchronousMessageForFailure()
{
// Expected return value of message() function of $this->context
$expectedReturnValue = [
"session_id" => null,
"user_id" => "we",
"access_jwt" => "C",
"access_token" => "john#gmail.com",
"refresh_token" => "C",
"refresh_token_expires" => "john#gmail.com"
];
// Expected return value of rawMessage() function of $this->context
$headerResponseExpected = [
'header' => [
'version' => '2.0',
'originId' => (string)Uuid::uuid4(),
'destination' => 'application/meta#1.0.0',
'sent' => '2017-12-19T10:12:37.941+00:00'
],
'content' => [
'session_id' => '1a92-4376-a8eb-deaf208ssess11',
'title' => "A task's title."
]
];
// Prediction of $this->context object starts
$this->context = $this->prophesize(MessageHandlingContextInterface::class);
$this->context->message(Argument::any())->willReturn($expectedReturnValue);
$encodedMessage = new JsonEncode($headerResponseExpected);
$rawMessage = ReceivedMessage::fromEncodedMessage($encodedMessage->asString());
$this->context->rawMessage()->willReturn($rawMessage);
$this->context->sendNonBlockingAsynchronous('platform_session_initiated', Argument::type("array"))->shouldNotBeCalled();
// Prediction of $this->context object ends
// Prophecy means prediction of the future object
$this->ravenRepository = $this->prophesize(HfxRepository::class);
// Mocking HfxProjectionHelper and calling the method updateReadModel which will return the string UpdateReadModel
$this->projectionHelper = $this->createMock(HfxProjectionHelper::class);
$this->projectionHelper->method('updateReadModel')->willReturn('UpdateReadModel');
// Actual calling
$broadcastPlatformSessionInitiated = new BroadcastPlatformSessionInitiated($this->ravenRepository->reveal(), $this->projectionHelper);
$response = $broadcastPlatformSessionInitiated->handleSynchronousMessage($this->context->reveal());
$this->assertInstanceOf('Gpx\Hfx\MessageTransport\Response\SendableResponse', $response);
$this->assertArrayHasKey("responseMessage", $response->content()->data());
$this->assertEquals("Failure. Session Id is not defined.", $response->content()->data()['responseMessage']);
}
}
This is the failure I am getting for testHandleSynchronousMessageForFailure
Gpx\Tests\Handlers\BroadcastPlatformSessionInitiatedTest::testHandleSynchronousMessageForFailure
Some predictions failed:
Double\MessageHandlingContextInterface\P3:
No calls have been made that match:
Double\MessageHandlingContextInterface\P3->sendNonBlockingAsynchronous(exact("platform_session_initiated"), type(array))
but expected at least one.
FAILURES!
Tests: 3, Assertions: 18, Failures: 1.
Could any one please clarify what I am doing wrong here?
Your setUp() method gets called before every test, so testHandleSynchronousMessageForFailure() is also expecting sendNonBlockingAsynchronous() to be called:
$this->context->sendNonBlockingAsynchronous('platform_session_initiated', Argument::type("array"))
->shouldBeCalled();
Even if you call shouldNotBeCalled() on it in the failure test. So, move the shouldBeCalled() call to the testHandleSynchronousMessageForSuccess(), that way it will expect it to be called in the success test, and not to be called in the failure test.
You should also tell PHPUnit to expect an IdNotDefinedException in the failure test:
$this->expectException(Gpx\Exceptions\IdNotDefinedException::class);

Laravel validation method not allowed

I'm trying to set validation on my controller method, but on validation failure I'm getting error that method is not allowed http exception.
My controller:
namespace App\Http\Controllers\Web;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Services\InvoicesService;
class InvoiceController extends Controller
{
private $invoice;
public function __construct(InvoicesService $invoice) {
$this->invoice = $invoice;
}
public function startNewInvoice($id, $customer)
{
$ticket = $this->invoice->getTicketByInvoice($id);
$ticket = $ticket->Ticket;
return view('form', ['InvoiceId' => $id,'CustomerInfo' => $customer, 'records' => null, 'recordState' => null, 'ticket' => $ticket, 'invoices' => null]);
}
public function generateInvoice(Request $request)
{
//dd($request);
$this->validate($request, [
'CustomerNumber' => 'required|numeric'
]);
$invoiceId = $request->input('Invoice');
$customer = array('CustomerCode' => $request->input('CustomerNumber'),'CustomerName' => $request->input('CustomerName'),'CustomerAddress' => $request->input('CustomerAddress'),
'CustomerVATCode' => $request->input('CustomerVatNumber'));
$hash = $this->invoice->generateInvoice($invoiceId, $customer);
$newInvoice = $this->invoice->newInvoice($request->input('CustomerNumber'), $hash->Id);
return $this->startNewInvoice($newInvoice->Id, $customer);
}
}
Any help would be really appreciated

Laravel Fractal transformer, how to pass and get extra variable

I'm using Dingo API to create an API in Laravel 5.2 and have a controller returning data with
return $this->response->paginator($rows, new SymptomTransformer, ['user_id' => $user_id]);
However, I don't know how to retrieve user_id value in the SymptomTransformer! Tried many different ways and tried looking into the class but I'm relatively new to both Laravel and OOP so if anyone can point me to the right direction, it'd be greatly appreciated.
Below is my transformer class.
class SymptomTransformer extends TransformerAbstract
{
public function transform(Symptom $row)
{
// need to get user_id here
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
}
You can pass extra parameter to transformer constructor.
class SymptomTransformer extends TransformerAbstract
{
protected $extra;
public function __construct($extra) {
$this->extra = $exta;
}
public function transform(Symptom $row)
{
// need to get user_id here
dd($this->extra);
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
}
And call like
return $this->response->paginator($rows, new SymptomTransformer(['user_id' => $user_id]));
You can set extra param via setter.
class SymptomTransformer extends TransformerAbstract
{
public function transform(Symptom $row)
{
// need to get user_id here
dd($this->test_param);
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
public function setTestParam($test_param)
{
$this->test_param = $test_param;
}
}
And then:
$symptomTransformer = new SymptomTransformer;
$symptomTransformer->setTestParam('something');
return $this->response->paginator($rows, $symptomTransformer);
If you are using Dependency Injection, then you need to pass params afterwards.
This is my strategy:
<?php
namespace App\Traits;
trait TransformerParams {
private $params;
public function addParam() {
$args = func_get_args();
if(is_array($args[0]))
{
$this->params = $args[0];
} else {
$this->params[$args[0]] = $args[1];
}
}
}
Then you implement the trait in your transformer:
<?php
namespace App\Transformers;
use App\Traits\TransformerParams;
use App\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
use TransformerParams;
public function transform(User $user)
{
return array_merge([
'id' => (int) $user->id,
'username' => $user->username,
'email' => $user->email,
'role' => $user->roles[0],
'image' => $user->image
], $this->params); // in real world, you'd not be using array_merge
}
}
So, in your Controller, just do this:
public function index(Request $request, UserTransformer $transformer)
{
$transformer->addParam('has_extra_param', ':D');
// ... rest of the code
}
Basically, the trait is a bag for extra params.

Unit Testing REST Update with Laravel and Mockery

I can't seem to figure out how I unit test the update of my controller. i'm getting the following error:
method update() from Mockery_0_App.... Should be called exactly 1 times but called 0 times.
After I remove the if statement in the update (after checking if the allergy exists), I get the following error on the line where I add the id the the unique validation rule:
Trying to get property of on object
My Code:
Controller:
class AllergyController extends \App\Controllers\BaseController
{
public function __construct(IAllergyRepository $allergy){
$this->allergy = $allergy;
}
...other methods (index,show,destroy) ...
public function update($id)
{
$allergy = $this->allergy->find($id);
//if ($allergy != null) {
//define validation rules
$rules = array(
'name' => Config::get('Patient::validation.allergy.edit.name') . $allergy->name
);
//execute validation rules
$validator = Validator::make(Input::all(), $rules);
$validator->setAttributeNames(Config::get('Patient::validation.allergy.messages'));
if ($validator->fails()) {
return Response::json(array('status' => false, 'data' => $validator->messages()));
} else {
$allergy = $this->allergy->update($allergy, Input::all());
if ($allergy) {
return Response::json(array('status' => true, 'data' => $allergy));
} else {
$messages = new \Illuminate\Support\MessageBag;
$messages->add('error', 'Create failed! Please contact the site administrator or try again!');
return Response::json(array('status' => false, 'data' => $messages));
}
}
//}
$messages = new \Illuminate\Support\MessageBag;
$messages->add('error', 'Cannot update the allergy!');
return Response::json(array('status' => false, 'data' => $messages));
}
}
TestCase:
class AllergyControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
$this->allergy = $this->mock('App\Modules\Patient\Repositories\IAllergyRepository');
}
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function tearDown()
{
parent::tearDown();
Mockery::close();
}
public function testIndex()
{
$this->allergy->shouldReceive('all')->once();
$this->call('GET', 'api/allergy');
$this->assertResponseOk();
}
...Other tests for Index and Show ...
public function testUpdate()
{
$validator = Mockery::mock('stdClass');
Validator::swap($validator);
$input = array('name' => 'bar');
$this->allergy->shouldReceive('find')->with(1)->once();
$validator->shouldReceive('make')->once()->andReturn($validator);
$validator->shouldReceive('setAttributeNames')->once();
$validator->shouldReceive('fails')->once()->andReturn(false);;
$this->allergy->shouldReceive('update')->once();
$this->call('PUT', 'api/allergy/1', $input);
$this->assertResponseOk();
}
}
Config validation rules file:
return array(
'allergy' => array(
'add' => array(
'name' => 'required|unique:Allergy'
),
'edit' => array(
'name' => 'required|unique:Allergy,name,'
),
'messages' => array(
'name' => 'Name'
)
)
);
Is there a way to actually mock the value provided into the validation rule? Or what is the best way to solve this?
I changed my code to this and now it works! :)
$validator = Mockery::mock('stdClass');
Validator::swap($validator);
$allergyObj = Mockery::mock('stdClass');
$allergyObj->name = 1;
$input = array('name' => 'bar');
$this->allergyRepo->shouldReceive('find')->with(1)->once()->andReturn($allergyObj);
$validator->shouldReceive('make')->once()->andReturn($validator);
$validator->shouldReceive('setAttributeNames')->once();
$validator->shouldReceive('fails')->once()->andReturn(false);;
$this->allergyRepo->shouldReceive('update')->once();
$this->call('PUT', 'api/allergy/1', $input);
$this->assertResponseOk();

Categories