How to mock an annotated AWS method in PHPUnit? - php

I am writing a unit test and I want to check whether the method publish() was called one or more times. This is a snippet from my whole test class:
<?php
namespace App\Tests\Unit;
use Aws\Sns\SnsClient;
use Exception;
use PHPUnit\Framework\TestCase;
class MyClassTest extends TestCase
{
/** #var SnsClient */
private $snsClient;
public function setUp(): void
{
$this->snsClient = $this->getMockBuilder(SnsClient::class)->disableOriginalConstructor()->getMock();
}
/**
* #throws Exception
*/
public function testNoCaseIdentifierSns()
{
$this->snsClient->expects($this->once())->method('publish')->with([
[
'doc_id' => 1,
'bucket' => 'some_bucket',
'key' => 'test.tiff/0.png'
],
'topic_arn'
]);
}
}
But when I run the code above I got the following error:
Trying to configure method "publish" which cannot be configured
because it does not exist, has not been specified, is final, or is
static
I guess the problem here is the method in AWS is defined as #method (see here):
* #method \Aws\Result publish(array $args = [])
It is possible to mock that method? What I am missing here?
UPDATE
After following comments suggestions I transformed my code into the following:
$this->snsClient->expects($this->once())->method('__call')->with([
'Message' => json_encode([
'doc_id' => 1,
'bucket' => 'some_bucket',
'key' => 'test.tiff/0.png'
]),
'TopicArn' => 'topic-arn'
]);
But now I am getting this other error:
Expectation failed for method name is equal to '__call' when invoked 1
time(s) Parameter 0 for invocation Aws\AwsClient::__call('publish',
Array (...)) does not match expected value. 'publish' does not match
expected type "array".
Why? publish() method signature is an array of args

From the thrown exception, we see that the __call function is called with the name of the target function (i.e. 'publish') and an array containing all arguments. As a result, here is how the mock setup can be updated:
$event = [
'Message' => json_encode([
'doc_id' => 1,
'bucket' => 'some_bucket',
'key' => 'test.tiff/0.png'
]),
'TopicArn' => 'topic-arn'
];
$this->snsClient
->expects($this->once())
->method('__call')
->with('publish', [$event]);

Related

What causes the 'Unknown format "factory"' error in this Laravel 8 app?

I am working on a Laravel 8 app with users and posts.
The objective is to create a bunch of posts (I already have users).
namespace Database\Factories;
// import Post model
use App\Models\Post;
// import User model
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory {
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Post::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition() {
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => $this->faker->factory(App\Models\User::class),
];
}
}
The problem
I run php artisan tinker then Post::factory()->count(100)->create() in the terminal and I get:
InvalidArgumentException with message 'Unknown format "factory"'
UPDATE
I replace my return statement with:
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => User::factory(),
];
I get this in the terminal:
Class 'Database\Factories\UserFactory' not found
Questions:
Where is my mistake?
Does the fact that I get the error Class 'Database\Factories\UserFactory' not found mean that I need to
create a UserFactory factory? Because there isn't one. (I wanted
to create posts, not users).
I don't suppose there is $this->faker->factory(..).
You can do
'user_id' => App\Models\User::factory()->create()->id,
EDIT:
'user_id' => App\Models\User::factory(),
Creating a UserFactory factory and using the below return statement did the trick:
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => User::factory(),
];
So, the PostFactory class looks like this:
class PostFactory extends Factory {
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Post::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition() {
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => User::factory(),
];
}
}

How do you convert third party API data to a collection resource in Laravel 5.6?

I've been working on creating a clean interface for our various web application and I've run into a snag with Laravel's API Resources not properly converting the incoming json array into laravel collections.
I can do it with a single resource:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
use App\Product;
class ProductResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'name' => $this->resource['product']['name'],
'description' => $this->resource['product']['description'],
'price' => $this->resource['product']['rental_rate']['price']
];
//return parent::toArray($request);
}
}
print this response outputs:
{"name":"Arri Skypanel S60-C","description":"Arri Sky Panel S60-C 450w input with a 2000w tungsten equivalent & Combo Stand","price":"260.0"}
However trying to take this single item and turn it into a collection of items isn't going anywhere.
Anybody got a clue what I'm missing?
Pulling the API data looks like this:
namespace App;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Client;
class ThirPartyAPI
{
private $url = 'https://api.third-party.com/api/v1/';
public function pull($query, $additionalParams) {
$client = new Client;
$result = $client->get($this->url . $query . $additionalParams, [
'headers' => [
'Content-Type' => 'application/json',
'X-AUTH-TOKEN' => env('CURRENT-AUTH-TOKEN'),
'X-SUBDOMAIN' => env('CURRENT-SUBDOMAIN')
]
]);
$array = json_decode($result->getBody()->getContents(), true);
return $array;
}
}
The API returns a lot of json data.
This is the Product model:
public function getAllProducts() {
try {
$productData = [];
$query = "/products?page=1&per_page=3&filtermode=active";
$additionalParams = "";
$productData = new ThirdPartyAPI;
$productData = $productData->pull($query, $additionalParams);
$productData = $productData['products'];
return ProductsResource::make($productData);
} catch (\Exception $ex) {
return $ex;
} catch (\Throwable $ex) {
return $ex;
}
}
Right now I'm trying something this to convert all the returned arrays into something I can control more:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class ProductsResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'products' => $this->collection->mapInto(function($request) {
return[ 'name' => $this->resource['name'],
'description' => $this->resource['description'],
'price' => $this->resource['rental_rate']['price']
];
})
];
}
However var_dumping the data just returns this:
object(App\Http\Resources\ProductsResource)[200]
public 'resource' =>
array (size=3)
0 =>
array (size=37)
'id' => int 164
'name' => string '10A Dimmer' (length=10)
[Lots of data]
...
'sale_rates' =>
array (size=0)
...
1 => .....
[cont]
public 'with' =>
array (size=0)
empty
public 'additional' =>
array (size=0)
empty
I've tried various forms of data conversion on the return json info and haven't had a lot of results except errors and confusing business. I'm a little shady on how Laravel handles API Resource handling.
Ok after some investigation into Laravel's 'make', 'mapinto' and 'map' methods for collections, I eventually got a working result from this conversion here:
$productData = ThirdPartyAPI;
$productData = $productData->pull($query, $additionalParams);
$productData = $productData['products'];
$products = collect($productData)->map(function($row){
return ProductsResource::make($row)->resolve();
});
var_dump($products);
That var_dump returns this:
object(Illuminate\Support\Collection)[228]
protected 'items' =>
array (size=3)
0 =>
array (size=3)
'name' => string '10A Dimmer' (length=10)
'description' => string '10amp Dimmer (Max 2.4k)' (length=23)
'price' => string '5.0' (length=3)
....
[And so on]
The initial information that was returned was a multidimensional array.
$returnedArray = array(
['array' => 1, 'name' => 'name', etc],
['array' => 2, 'name' => 'name, etc]
);
Laravel's default collection method only turns the top array into a collection. In order to properly be able to control the results via the Resource models we have to convert the whole set of arrays to collections, which means we have to iterate through the returned data to convert it to something laravel can read properly. That's what the map method does.
According to the docs it, 'The map method iterates through the collection and passes each value to the given callback. The callback is free to modify the item and return it, thus forming a new collection of modified items'
the make method creates a new collection instance. I don't know what the resolve function does except that the docs mention that it 'resolves a given class or interface name to its instance using the service container'. I'm going to assume it means that it makes sure passes through the class properly?
Anyway I hope that helps people in the future.

Laravel: Custom validation messages in Custom Form Requests

I am trying to keep my controller clean and move the custom request validation in to a separate class as:
public function register(RegisterUserRequest $request)
and in there define all the usual functions, such as
public function rules(),
public function messages(), and
public function authorize()
However, the frontend is expecting the following data to display:
title (title related to the validation error message), description (which is the the actual validation error message), and status (=red, yellow etc)
How can I actually customise the response of the request?
Something like this, does not seem to be working:
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
{
$response = new Response(['data' => [],
'meta' => [
'title' => 'Email Invalid'
'description' => '(The error message as being returned right now)',
'status' => 'red'
]);
throw new ValidationException($validator, $response);
}
Any ideas?
you can do this by making request in laravel as the following :
php artisan make:request FailedValidationRequest
this command will create class called FailedValidationRequest in
App\Http\Request directory and you cab write your rules inside this class as the following:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class FailedValidationRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'title' => 'required'
'description' => 'required',
'status' => 'required|numeric'
];
}
}
and if you want to customize the error message you can download the language you want from this link using composer:
https://packagist.org/packages/caouecs/laravel-lang
and write the language you want by copying the languge folwder to the lang directory in your resource folder.
'custom' => [
'title' => [
'required' => 'your custom message goes here',
],
'decription' => [
'required' => 'your custom message goes here',
],
],

How to determine why a PHPUnit mock fails?

I want to unit test following class.
<?php
namespace Gpx\Handlers;
use Gpx\EntityInfrastructure\Model\Events\SessionInvalidated;
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\HfxProjectionHelper;
use Gpx\HfxEventSourcing\HfxEventMetadata;
use Gpx\HfxEventSourcing\HfxRepository;
use Gpx\Hfx\MessageTransport\Response\SendableResponse;
class BroadcastSessionInvalidated implements SynchronousHandlerInterface
{
/** #var HfxRepository */
private $repository;
/** #var HfxProjectionHelper */
private $projectionHelper;
public function __construct(HfxRepository $repository, HfxProjectionHelper $projectionHelper)
{
$this->repository = $repository;
$this->projectionHelper = $projectionHelper;
}
public function handleSynchronousMessage(MessageHandlingContextInterface $context): SendableResponse
{
$content = $context->message();
$header = $context->rawMessage()->header();
$metadata = HfxEventMetadata::fromHfxHeader($header);
$payload = Payload::fromMessageContent($content);
/** #var HfxAggregateRoot $roleAggregate */
// Gpx\HfxEventSourcing\HfxAggregateRoot
$roleAggregate = $this->repository->get($payload->id());
$roleAggregate->registerEvent(SessionInvalidated::class, $payload, $metadata);
$this->repository->save($roleAggregate);
$currentEvent = $roleAggregate->currentEvent();
$context->sendNonBlockingAsynchronous('session_invalidated',$content);
$this->projectionHelper->updateReadModel();
return SendableResponse::answerTo($context->rawMessage(), 1100, [
'responseMessage' => 'Success',
'event' => $currentEvent
]);
}
}
Test case I have written so far
<?php
namespace Gpx\Tests\Feature;
use Ramsey\Uuid\Uuid;
use Gpx\Json\JsonEncode;
use Prophecy\Argument;
use PHPUnit\Framework\TestCase;
use Gpx\HfxEventSourcing\Hfx;
use Gpx\HfxEventSourcing\HfxRepository;
use Gpx\Hfx\Framework\MessageTransportApplication\Handler\MessageHandlingContextInterface;
use Gpx\Handlers\BroadcastSessionInvalidated;
use Gpx\Hfx\MessageTransport\Message\ReceivedMessage;
use Prooph\EventSourcing\AggregateRoot;
use Gpx\HfxEventSourcing\HfxAggregateRoot;
class BroadcastSessionInvalidatedTest extends TestCase
{
/** #var HfxRepository */
private $repository;
/** #var Hfx */
private $projectionHelper;
// We have to test handleSynchronousMessage handler whether it is returning sendable response with certain properties in it.
public function testHandleSynchronousMessage()
{
// Expected return value of message() function of $context
$expectedReturnValue = [
"session_id" => "1a92-4376-a8eb-deaf208e1",
"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 $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' => "8365526e-fb92-4376-a8eb-deaf208edf61",
'title' => "A task's title."
]
];
// Prophecy means prediction of the future object
// Prediction of $context object starts
$context = $this->prophesize(MessageHandlingContextInterface::class);
$context->message(Argument::any())->willReturn($expectedReturnValue);
$encodedMessage = new JsonEncode($headerResponseExpected);
$rawMessage = ReceivedMessage::fromEncodedMessage($encodedMessage->asString());
$context->rawMessage()->willReturn($rawMessage);
$context->sendNonBlockingAsynchronous('platform_session_initiated', Argument::type("array"))
->shouldBeCalled();
// Prediction of $context object ends
// Repository Mocking Starts
$ravenAggregateRoot = $this->getMockBuilder(HfxAggregateRoot::class)
->disableOriginalConstructor()
->getMock();
$this->ravenRepository = $this->prophesize(HfxRepository::class);
$this->ravenRepository->get('1a92-4376-a8eb-deaf208e1')->shouldBeCalled()->willReturn($ravenAggregateRoot);
$this->ravenRepository->save(Argument::any())->shouldBeCalled();
// Repository Mocking Ends
// Mocking Hfx and calling the method updateReadModel which will return the string UpdateReadModel
$this->projectionHelper = $this->createMock(Hfx::class);
$this->projectionHelper->method('updateReadModel')
->willReturn('UpdateReadModel');
// Actual calling
$broadcastPlatformSessionInvalidated = new BroadcastSessionInvalidated($this->ravenRepository->reveal(), $this->projectionHelper);
//$broadcastPlatformSessionInvalidated = new BroadcastSessionInvalidated($this->ravenRepository, $this->projectionHelper);
$response = $broadcastPlatformSessionInvalidated->handleSynchronousMessage($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']);
}
}
When I am executing the test it throws an error
PHPUnit 6.5.4 by Sebastian Bergmann and contributors.
.E 2 / 2 (100%)
Time: 506 ms, Memory: 6.00MB
There was 1 error:
1) Gpx\Tests\Feature\BroadcastSessionInvalidatedTest::testHandleSynchronousMessage
Prophecy\Exception\Call\UnexpectedCallException: Method call:
- sendNonBlockingAsynchronous("session_invalidated", ["session_id" => "1a92-4376-a8eb-deaf208e1", "user_id" => "we", "access_jwt" => "C", "access_token" => "john#gmail.com", "refresh_token" => "C", "refresh_token_expires" => "john#gmail.com"])
on Double\MessageHandlingContextInterface\P3 was not expected, expected calls were:
- message(*)
- rawMessage()
- sendNonBlockingAsynchronous(exact("platform_session_initiated"), type(array))
/vagrant/services/sessions-stream/app/src/Gpx/Handlers/BroadcastSessionInvalidated.php:54
/vagrant/services/sessions-stream/app/tests/Feature/BroadcastPlatformSessionInvalidatedTest.php:107
ERRORS!
Tests: 2, Assertions: 6, Errors: 1.
What I am doing wrong here?
The Exception tells you what's wrong: you use shouldBeCalled() to tell PHPUnit to check that the method sendNonBlockingAsynchronous() gets called with first parameter platform_session_initiated and an array in the second parameter.
// BroadcastSessionInvalidatedTest
$context->sendNonBlockingAsynchronous('platform_session_initiated', Argument::type("array"))
->shouldBeCalled();
The second condition is met, but in your actual method the first parameter is session_invalidated:
// BroadcastSessionInvalidated
$context->sendNonBlockingAsynchronous('session_invalidated',$content);
So change the expectation to be
// BroadcastSessionInvalidatedTest
$context->sendNonBlockingAsynchronous('session_invalidated', Argument::type("array"))
->shouldBeCalled();
And it will work.

Laravel 5.1 ERROR Call to undefined function App\Http\Controllers\Auth\sendRegistermail()

I am trying to send automated mails via Mandrill in my Laravel 5.1 project. It was working but I was setting up my Mandrill Calls in my AuthController now I wanna have a class App\Marketing where all my functions for sending email will be stored. So in my controllers after an actions happens I can just call up the function with 1 line of code, but this line is giving me troubles I think.
my App\Marketing class looks like this now
class Marketing{
private $mandrill;
/**
* Via construct injection
*
*/
public function __construct(Mail $mandrill)
{
$this->mandrill = $mandrill;
}
public function sendRegistermail()
{
// In template content you write your dynamic content if you use <mc:edit> tags.
$template_content = [];
$message = array(
'subject' => 'Welkom bij SP*RK! - Jouw accountgegevens',
'from_email' => 'noreply#spark.com',
'from_name' => 'SP*RK',
'to' => array(
array(
'email' => $request->input('email'),
'name' => $request->input('name'),
'type' => 'to'
)
),
'merge_vars' => array(
array(
'rcpt' => $request->input('email'),
'vars' => array(
array(
'name' => 'NAME',
'content' => $request->input('name')
),
array(
'name' => 'EMAIL',
'content' => $request->input('email')
)
)
)
)
);
//email validation
if (str_contains($request['email'], "#kuleuven.be")) {
MandrillMail::messages()->sendTemplate('registration-mail', $template_content, $message);
} else {
MandrillMail::messages()->sendTemplate('registration-mail-notactive', $template_content, $message);
}
}
// ----- OR -------
/**
* Via method injection
*
*/
public function sendMail(Mail $mandrill, $data)
{
$mandrill->messages()->sendTemplate($data)
}
// ----- OR -------
/**
* Via the Facade
*
*/
public function sendMailByFacade($data)
{
\MandrillMail::messages()->sendTemplate($data);
}
}
This is how I try to call the function after registration in my postRegister function:
sendRegistermail();
return redirect($this->redirectPath());
sendRegistermail is a method of your Marketing class, you should call it on an instance of that object
So, first of all you have to create a Marketing object instance in your controller. A good way to do this it's by injecting the dependency in the constructor, like this:
//your controller class
class Controller
{
protected $marketing;
//Your controller's constructor
public function __construct(Marketing $marketing)
{
$this->marketing = $marketing;
}
}
Or you can use one of the other methods you have provided in your code to inject the instance.
Once you have an instance of the Marketing class, you only need to call the sendRegistermail method on that instance. In your controller method:
//call the method on the marketing instance
$this->marketing->sendRegistermail();

Categories