Feature test for HTTP RESTful API on Laravel 8 - php

I'm trying to write a feature test in RESTful API that I have been created in laravel 8.
I have ChatController that gets user chats by getUserChats method:
// get chats for user
public function getUserChats()
{
$chats = $this->chats->getUserChats();
return ChatShowResource::collection($chats);
}
that called by this route:
Route::get('chats', [
App\Http\Controllers\Chats\ChatController::class, 'getUserChats'
]);
this is my feature test on this route:
public function test_get_user_chats()
{
// creates the fake user
$user = User::factory()->create();
$this->actingAs($user, 'api');
// adds the chat to new fake user that created
$response_create_chat = $this->json('POST', '/api/chats', [
'recipient' => $user->username,
'body' => 'some chat text',
]
);
$response_create_chat
->assertStatus(201)
->assertJsonPath('data.body', 'some chat text');
$response_get_chats = $this->json('get', '/api/chats');
$response_get_chats->assertStatus(200);
}
the test runs successfully by this green output OK (1 test, 3 assertions) but I don't know this is the best way to implementing that.
so the question is that anything else I have to do in my test? am I doing the right feature test?

Generally, you want the scope of your test to be as small as possible. The test case that you provided is currently testing two separate api endpoints:
Creating chats
Getting chats
The issue with this is that if code related to creating chats breaks, your test case related to getting chats will fail.
I would recommend splitting these into two separate test cases. This will allow you to trust that if your test case for getting chats fails, it's as a result of the code relating to getting chats failing rather than code unrelated to this endpoint.
I would also recommend using setUp and tearDown methods to ensure that your test case leaves the database in the same state that it found it. This ensures that other test cases will not have to concern themselves with the data from this test. This means the setUp method should do things such as:
Creating users
Creating chats
The tearDown method would be responsible for deleting the users and chats.
Additionally, you should test the format and data that is output by your endpoint. At the moment you are only checking for a 200 response code. If your api were updated to return a list of users rather than chats, the test case related to getting chats would still pass as it does not actually validate the data that is being returned.
EX:
class GetChatsTest extends TestCase
{
private User $user;
public function testGetChats(): void
{
$chat = $this->json('get', '/api/chats');
// Check the chat's returned value
$this->assertSame('Some parameters', $chat);
}
protected function setUp(): void
{
parent::setUp();
// Create the user in the setup. This is run before each test method is run.
$this->user = User::factory()->create();
// Create the chat for the user (Pseudo code because I don't know how your code works)
$this->user->createChat('Some parameters');
}
protected function tearDown(): void
{
parent::tearDown();
// Delete the user and chats here, pseudocode again. This runs after every test method is run
$this->user->deleteChats();
$this->user->delete();
}
}
One thing to note about the example code is that it does not use the rest api to create the user or the chat but instead uses pseudocode controller methods to ensure that it isn't dependent on the functionality of the chat api for testing.

Related

Laravel Event Sourcing (Spatie) - Using projections within business rules

I know that the general concept behind event sourcing is that the state of the application should be able to be replayed from the event stream.
Sometimes, however, we need to get information for business rules from other parts of the system. i.e. An account has a user. A user has a blacklist status which is required to check if they can access/edit the account.
In the below example (purely for demonstration purposes), a user tries to subtract $10 from their account. If a user has been blacklisted, then we do not want to allow them to remove any funds from the account but we do want to record that they have tried to.
After the request is made, we could query the user model to see if the blacklist exists. If true then we can record it and throw the exception.
The user table/model is currently not event-sourced.
Now when we try to replay the event stream to re-build the projections with the state of the user is not being stored in events, it is no longer possible.
So assuming my current example does not work my questions are:
If we were to move the user into an event stored system (in a different aggregate but all events within the same event-stream) then would it be acceptable to use read models within business rules?
Is there any way we can mix event-sourced and CRUD into the same system when they may depend on each other for business rules.
public function subtractMoney(int $amount)
{
if ($this->accountOwnerIsBlacklisted()){
$this->recordThat(new UserActionBlocked());
throw CouldNotSubtractMoney::ownerBlocked();
}
if (!$this->hasSufficientFundsToSubtractAmount($amount)) {
$this->recordThat(new AccountLimitHit());
if ($this->needsMoreMoney()) {
$this->recordThat(new MoreMoneyNeeded());
}
$this->persist();
throw CouldNotSubtractMoney::notEnoughFunds($amount);
}
$this->recordThat(new MoneySubtracted($amount));
}
private function accountOwnerIsBlacklisted(): bool
{
return $this->accountRepositry()->ownerUser()->isBlackListed();
}
Since you are basically working with DDD (without mentioning it) the answer could lie in the definitions there. In DDD you are supposed to define the boundaries of each aggregate root. Each aggregate root should not store any dependencies to other aggregate roots (The Spatie package doesn't even support it). It should only be made up of the events, which then become the single source of truth.
Given your example, it seems that the blocking of a user is not due to negative events on his account, but rather due to something that happened in relation to his user (account owner). The keyword here seems to be "owner". If you want to store the fact that the user action of trying to withdraw money happened, then you could still apply the event, but the reason would, in this case, come from another aggregate "the user". It doesn't matter if the user itself is event sourced, but the user entity has the method to check if the user is blocked, and therefore it is the business rule in your system that he is not allowed to make withdrawals from the account.
If you cannot model these two together, then I would suggest that you design a domain service which can handle this command. Try to keep them as a part of your model to avoid making your domain model anaemic, if you can.
<?php
class AccountWithdrawalService
{
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function withdraw($userId, $accountId, $amount)
{
$user = $this->userRepository->find($userId);
// You might inject AccountAggregateRoot too.
$account = AccountAggregateRoot::retrieve($accountId);
if(!$user->isBlackListed())
{
$account->subtractMoney($amount);
}
else
{
// Here we record the unhappy road :-(
$account->moneySubtractionBlocked($amount);
}
$account->persist();
}
}
PS: A further possibility is to inject your userRepository in the actual method handling the withdrawal, as long as the userRepository is not a full dependency of the AccountAggregateRoot. This, I believe, is highly discussed.
Give the Account a UserBlacklistedEvent and use this for validation in the Account. Don't record failed attempts unless thats a reporting requirement.
<?php
class Account {
private bool blacklisted;
function blacklist() {
$this->recordThat(new Blacklisted());
}
public function subtractMoney(int $amount)
{
if ($this->blacklisted) {
throw new DomainException("Why on earth has this backend developer let fella make queries to their account. fk em - wait call security we being attaced");
}
...
<?php
class BlacklistService
{
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function blacklist($userId, $accountId)
{
$user = $this->userRepository->find($userId);
// You might inject AccountAggregateRoot too.
$account = AccountAggregateRoot::retrieve($user->accountUuid);
$user->blacklist();
$user->save();
$account->blacklisted(); // no need for userid, in most cases
$account->persist();
}
}
<?php
class AccountCreateService
{
function create() {
$user = $this->userRepository->find($userId);
if ($user->blacklisted) {
throw new DomainException("Why on earth has the frontend developer called this.");
}
$account = AccountAggregateRoot::retrieve($user->accountUuid);
...
Even better: don't throw a Exception in Account AR. just return void or and $this->recordThat(new SubtractionEventOnBlacklistedEvent). Since its invariant is protected. Why throw an event? or if its invarient is protected then the invalid case ins't an exceptional circumstance
The exception after all won't do anything for the user. Its unlightly you wan't them reading the exception screen. And then in this case, you could setup some alternate logging perhaps

Is there a way to act as a user inside of Laravel without hitting the database?

I'm writing unit tests for an API using PHPUnit and Laravel. Most functions I'm testing require that the user is authenticated before the function can be ran. The user data is stored in one table, and their permissions are stored inside of another table. I can fake the user object inside of Laravel, but I need to be able to also pull the corresponding permissions from the other table without having to hit the database like the dingo router currently is doing.
Currently running Laravel 5.8 and PHPUnit 8.1.5. I currently have the users object that I generated from a Laravel factory saved to a text file. I am able to pass that to a function called "actingAsApi" (found on Github, code below) and that allows me to authenticate as that user. However, the function is still going out and getting all permissions for that user from the database. I'm trying to mock or fake the permissions object it is pulling somewhere so that it doesn't need to hit the database at all. I also tried using the built in Passport functions for Passport::actingAs, and those did not work either as they were still hitting the DB (and not really working anyways).
actingAsApi (inside of TestCase.php)
protected function actingAsApi($user)
{
// mock service middleware
$auth = Mockery::mock('Dingo\Api\Http\Middleware\Auth[handle]',
[
Mockery::mock('Dingo\Api\Routing\Router'),
Mockery::mock('Dingo\Api\Auth\Auth'),
]);
$auth->shouldReceive('handle')
->andReturnUsing(function ($request, \Closure $next) {
return $next($request);
});
$this->app->instance('Dingo\Api\Http\Middleware\Auth', $auth);
$auth = Mockery::mock('Dingo\Api\Auth\Auth[user]',
[
app('Dingo\Api\Routing\Router'),
app('Illuminate\Container\Container'),
[],
]);
$auth->shouldReceive('user')
->andReturnUsing(function () use ($user) {
return $user;
});
$this->app->instance('Dingo\Api\Auth\Auth', $auth);
return $this;
}
Test inside of my Test file
public function testActAs() {
$user = 'tests/users/user1.txt';
$this->actingAsApi($user);
$request = new Request;
$t = new TestController($request);
$test = $t->index($request);
}
I expect the actingAsApi function to allow me to also pass in the mock permissions data that corresponds to my mock user object data from the file, but instead it is hitting the database to pull from the permissions table.
EDIT:
So i've been playing around with doing mock objects, and i figured out how to mock the original controller here:
$controlMock = Mockery::mock('App\Http\Controllers\Controller', [$request])->makePartial();
$controlMock->shouldReceive('userHasPermission')
->with('API_ACCESS')
->andReturn(true);
$this->app->instance('App\Http\Controllers\Controller', $controlMock);
but now I can't figure out how to get my call from the other controllers to hit the mocked controller and not a real one. Here is my code for hitting an example controller:
$info = $this->app->make('App\API\Controllers\InfoController');
print_r($info->getInfo('12345'));
How can i make the second block of code hit the mocked controller and not standup a real one like it does in its constructor method?
Finally came on an answer, and it is now fixed. Here's how I did it for those wondering:
$request = new Request;
$controlMock = m::mock('App\API\Controllers\InfoController', [$request])->makePartial();
$controlMock->shouldReceive('userHasPermission')
->with('API_ACCESS')
->andReturn(true);
print_r($controlMock->getInfo('12345'));
Basically, I was trying to Mock the original API controller, and then catch all of the calls thrown at it. Instead, I should've been mocking the controller I'm testing, in this case the InfoController. I can then catch the call 'userHasPermission', which should reach out to the Controller, but I am automatically returning true. This eliminates the need for hitting the database to receive permissions and other info. More information on how I solved it using Mockery can be found here: http://docs.mockery.io/en/latest/cookbook/big_parent_class.html. As you can see, this is referred to as a 'Big Parent Class'. Good luck!

Why do I have to reload Auth::user() when doing integration testing

This is a follow up of How to wait for a page reload in Laravel integration testing
What I am doing is to edit a user's profile and then redisplay the view.
My profile action: (UserController)
public function profile(){
return view('user/profile');
}
The view contains code like
{{ Auth::user()->firstname }}
now during my test, the old (unchanged) user data is displayed.
The test:
protected function editUserProfile()
{
$this->visit('/user/profile');
$firstName = $this->faker->firstname;
$lastname = $this->faker->lastname;
$this->within('#userEditForm', function() use ($firstName, $lastname) {
$this->type($firstName, 'firstname');
$this->type($lastname, 'surname');
$this->press('Save')
->seePageIs('/user/profile')
->see($firstName) # here the test fails
->see($lastname);
});
}
When I change the UserController like this:
public function profile(){
Auth::setUser(Auth::user()->fresh());
return view('user/profile');
}
everything works fine.
Now I want to understand, why that is like this.
Why does the integration test behave differently to the browser in that case? Is there a better way to align that behavior so the tests do only fail if there is a "real problem"? Or is my code just bad?
You're probably using update (int $uid) for the request?
The most likely explanation is that Laravel only uses a single application instance during the test. It's taking the input you give it, building a request object, and then sending it to the controller method. From here it can render the view and check that it contains your text.
In the authentication implementation once you call Auth::user() it doest one of two things:
If no user is loaded it attempts to retrieve it from storage.
If a user is already loaded it returns it.
Your update method (I'm guessing) is retrieving a new instance of the user from storage and updating it, not the cached one.
For example:
\Auth::loginUsingId(1234);
\Auth::user()->email; // 'user#example.com'
$user = \App\User::find(1234);
$user->email; // 'user#example.com';
$user->update(['email' => 'user2#example.com']);
$user->email; // 'user2#example.com'
\Auth::user()->email; // 'user#example.com'

How to get data back from a command bus?

I'm fairly new to domain driven design concepts and I've run into a problem with returning proper responses in an API while using a command bus with commands and command handlers for the domain logic.
Let's say we’re building an application with a domain driven design approach. We have a back end and front end portion. The back end has all of our domain logic with an exposed API. The front end uses the API to make requests to the application.
We're building our domain logic with commands and command handlers mapped to a command bus. Under our Domain directory we have a command for creating a post resource called CreatePostCommand. It's mapped to its handler CreatePostCommandHandler via the command bus.
final class CreatePostCommand
{
private $title;
private $content;
public function __construct(string $title, string $content)
{
$this->title = $title;
$this->content= $content;
}
public function getTitle() : string
{
return $this->title;
}
public function getContent() : string
{
return $this->content;
}
}
final class CreatePostCommandHandler
{
private $postRepository;
public function __construct(PostRepository $postRepository)
{
$this->postRepository = $postRepository;
}
public function handle(Command $command)
{
$post = new Post($command->getTitle(), $command->getContent());
$this->postRepository->save($post);
}
}
In our API we have an endpoint for creating a post. This is routed the createPost method in a PostController under our Application directory.
final class PostController
{
private $commandBus;
public function __construct(CommandBus $commandBus)
{
$this->commandBus = $commandBus;
}
public function createPost($req, $resp)
{
$command = new CreatePostCommand($command->getTitle(), $command->getContent());
$this->commandBus->handle($command);
// How do we get the data of our newly created post to the response here?
return $resp;
}
}
Now in our createPost method we want to return the data of our newly created post in our response object so our front end application can know about the newly created resource. This is troublesome since we know that by definition the command bus should not return any data. So now we're stuck in a confusing position where we don't know how to add our new post to the response object.
I'm not sure how to proceed with this problem from here, several questions come to mind:
Is there an elegant way to return the post's data in the response?
Am I incorrectly implementing the Command/CommandHandler/CommandBus pattern?
Is this simply just the wrong use case for the Command/CommandHandler/CommandBus pattern?
First, notice that if we wire the controller directly to the command handler, we face a similar problem:
public function createPost($req, $resp)
{
$command = new CreatePostCommand($command->getTitle(), $command->getContent());
$this->createPostCommandHandler->handle($command);
// How do we get the data of our newly created post to the response here?
return $resp;
}
The bus is introducing a layer of indirection, allowing you to decouple the controller from the event handler, but the problem you are running into is more fundamental.
I'm not sure how to proceed with this problem from here
TL;DR - tell the domain what identifiers to use, rather than asking the domain what identifier was used.
public function createPost($req, $resp)
{
// TADA
$command = new CreatePostCommand($req->getPostId()
, $command->getTitle(), $command->getContent());
$this->createPostCommandHandler->handle($command);
// happy path: redirect the client to the correct url
$this->redirectTo($resp, $postId)
}
In short, the client, rather than the domain model or the persistence layer, owns the responsibility of generating the id of the new entity. The application component can read the identifier in the command itself, and use that to coordinate the next state transition.
The application, in this implementation, is simply translating the message from the DTO representation to the domain representation.
An alternative implementation uses the command identifier, and derives from that command the identities that will be used
$command = new CreatePostCommand(
$this->createPostId($req->getMessageId())
, $command->getTitle(), $command->getContent());
Named UUIDs are a common choice in the latter case; they are deterministic, and have small collision probabilities.
Now, that answer is something of a cheat -- we've really only demonstrated that we don't need a result from the command handler in this case.
In general, we would prefer to have one; Post/Redirect/Get is a good idiom to use for updating the domain model, but when the client gets the resource, we want to make sure they are getting a version that includes the edits they just made.
If your reads and writes are using the same book of record, this isn't a problem -- whatever you read is always the most recent version available.
However, cqrs is a common architectural pattern in domain driven design, in which case the write model (handling the post) will redirect to the read model -- which is usually publishing stale data. So you may want to include a minimum version in the get request, so that the handler knows to refresh its stale cache.
Is there an elegant way to return the post's data in the response?
There's an example in the code sample you provided with your question:
public function createPost($req, $resp)
Think about it: $req is a representation of the http request message, which is roughly analogous to your command, and $resp is essentially a handle to a data structure that you can write your result into.
In other words, pass a callback or a result handle with your command, and let the command handler fill in the details.
Of course, that depends on your bus supporting callbacks; not guaranteed.
Another possibility, which doesn't require changing the signature of your command handler, is to arrange that the controller subscribes to events published by the command handler. You coordinate a correlation id between the command and the event, and use that to pull up the result event that you need.
The specifics don't matter very much -- the event generated when processing the command could be written to a message bus, or copied into a mailbox, or....
I am using this approach and I am returning command results. However, this is a solution which works only if the command handlers are part of the same process. Basically, I'm using a mediator, the controller and the command handler get an instance of it (usually as a constructor dependency).
Pseudo code controller
var cmd= new MyCommand();
var listener=mediator.GetListener(cmd.Id);
bus.Send(cmd);
//wait until we get a result or timeout
var result=listener.Wait();
return result;
Pseudo code command handler function
var result= new CommandResult();
add some data here
mediator.Add(result,cmd.Id);
That's how you get immediate feedback. However, this shouldn't be used to implement a business process.
Btw, this has nothing to do with DDD, it's basically a message driven CQS approach which can be and it is used in a DDD app.

Symfony 2 stop saving form process

I am trying to link my local users stored in the database with some external services, so they can login anywhere with the same credentials.
I have an EventListener waiting for some FOSUserEvents like FOSUserEvents::REGISTRATION_SUCCESS or FOSUserEvents::CHANGE_PASSWORD_SUCCESS, fired when the data are valid but not actually saved, to perform some call to the various external services and replicate the new user credentials.
If the services return a message saying that the data are replicated, everything work fine.
But if a service say that there is a problem, no mater what it is, I would like to interrupt the saving process of the form and adding an error message.
The objective is to prevent Symfony to save the data, even if they are valid, if an external service say no, and I don't know how to perform this kind of emergency stop.
Actually, it's only based on FOSUserBundle but if I find a working solution, I will have to execute something similar for other entities, that why I try to be as generic as possible.
Here a some of my code
class MyListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'createUser',
FOSUserEvents::CHANGE_PASSWORD_SUCCESS => 'editUser'
);
}
public function createUser(FormEvent $event)
{
$user = $event->getForm()->getData();
// Check if the user exists before trying to edit
$result = $this->_userExists($user);
if($result['value'] == false)
{
// Create the user
$result = $this->_createUser($user);
// Check if the user is successfully replicated
if($result['result'] != 'success')
{
/*
* Emergency stop (user not replicated)
*/
}
}else{
/*
* Emergency stop (user doesn't exists)
*/
}
}
. . .
}
Actually I didn't find any way to stop the saving workflow and returning to the form page to display an error message. So if somebody has an idea of how to perform this, feel free so write your idea, and thanks you all guys. Stackoverflow is really the best ;-)

Categories