Trying to get to grips with Mocking and test cases, I want to test that a Mailable TestMail is sent from company#company.com, the documentation provides hasTo, hasCc, and hasBcc but doesn't look like it uses something like hasFrom. Is there any solutions to this?
https://laravel.com/docs/9.x/mocking#mail-fake
public function testEmailAlwaysFrom()
{
Mail::fake();
Mail::to('foo#bar.com')->send(new TestMail);
Mail::assertSent(TestMail::class, function ($mail) {
return assertEquals('company#company.com', $mail->getFrom());
// return $mail->hasTo($user->email) &&
// $mail->hasCc('...') &&
// $mail->hasBcc('...');
});
}
MailFake doesn't provide hasFrom method in the class and therefore will return false.
The workaround below however doesn't work when using the environmental variable MAIL_FROM_ADDRESS, ->from() has to be called within build().
A couple of GitHub issues have been reported suggesting a workaround below:
https://github.com/laravel/framework/issues/20056
https://github.com/laravel/framework/issues/20059
public function testEmailAlwaysFrom()
{
Mail::fake();
Mail::to('foo#bar.com')
->send(new TestMail);
Mail::assertSent(TestMail::class, function ($mail) {
$mail->build(); // <-- workaround
return $mail->hasTo('foo#bar.com') and
$mail->hasFrom('company#company.com');
});
}
Related
It is not quite clear to me when I should use integrations and unit test
If I want to test the following code without making any http requests, should I use an integration test or unit ?
When a thread is created, the activity is recorded and an email event is fired
ThreadObserver extends Observer
{
public function created(Thread $thread)
{
Activity::record($thead);
event(new ThreadWasCreated($thread);
}
}
class RecordsActivityTest
{
public function it_records_the_acitivty_when_a_thread_is_created()
{
Thread::factory()->create();
$this->assertDatabaseHas('activities', […]);
}
}
Another example, I want to test that give a query it returns the expected results .
class Search
{
public function __construct(protected Index $index)
{
}
public function handle($query)
{
$this->index->search($query);
// more code
}
}
class SearchTest
{
public function it_returns_the_expected_result()
{
$search = new Search(new Index);
$results = $search->handle(“some query”);
$this->assertEquals(“some
Data”);
}
}
Do I need a unit test, to test the Search class or an integration test because it is dependent on another class ?
The first example, I'd most likely write an integration test, but instead of testing if the database has a value, I'd just mock the event https://laravel.com/docs/8.x/mocking#event-fake and assert that the event was dispatched.
The second example however, it could go either way. You can write a unit test and just mock everything OR, you could write an integration test so you can use the Laravel helpers, specifically the https://laravel.com/docs/8.x/mocking#http-fake if you're testing an API, otherwise mockery if you're testing and SDK for example.
I am implementing payments for my website using the API of an external service (ie. the service of the payment provider).
Let's say the user clicks 'BUY', and then we go to my controller which says something along the lines of:
public function buyFunction() {
$result = $this->ExternalService->pay();
if ($result->success == true) {
return 'We are happy';
}
}
I have also created the aforementioned externalService which has the pay() method:
class ExternalService {
public function pay() {
response = //Do stuff with Guzzle to call the API to make the payment
return response;
}
}
Now, sometimes things go wrong.
Let's say the API returns an error - which means that it throws a GuzzleException - how do I handle that?
Ideally, if there is an error, I would like to log it and redirect the user to a page and tell him that something went wrong.
What I've tried
I have tried using a try/catch statement within the pay() function and using abort(500) but this doesn't allow me to redirect to the page I want to.
I have tried using a try/catch statement within the pay() function and using return redirect('/mypage') but this just returns a Redirect object to the controller, which then fails when it tries to call result->success
I have tried using number 2 but also adding a try/catch block to the controller method, but nothing changed.
In the end, I have found two solutions. In both, I use a try/catch block inside the pay() method. Then I either return 0; and check in the controller if (result == 0) or I use abort( redirect('/mypage') ); inside the try/catch block of the pay() method.
What is the right way to handle this?
How to use the try/catch blocks?
In my experience, avoid handling exceptions let them pass through and handle them accordingly with try catches. This is the most pragmatic approach. Alternatively you will end up checking result is correct in weird places, eg. if ($result) {...}. Just assume it went good, except if the exception is thrown. Bonus: never do Pokemon catches with Exception $e unless you specifically needs it!
class ExternalService {
public function pay() {
try {
response = $client->get(...);
} catch (BadResponseException $exception) {
Log::warning('This should not happen check payment api: ' . $exception->getMessage());
throw new PaymentException('Payment did not go through');
}
return response;
}
}
Assuming you have your own Exception.
class PaymentException extends HttpException
{
public function __construct(?\Exception $previous = null)
{
parent::__construct(Response::HTTP_BAD_REQUEST, 'Unexpected error processing the payment', $previous);
}
}
This enables you to handle the flow in a controller, where it would make sense to handle the redirect. Sometimes if the exception is very integral or common to the web app, it can also be handled by the exception handler instead.
class PaymentController {
public function pay(PaymentService $service) {
try {
$payment = $service->buyFunction();
} catch (PaymentException $exception) {
return redirect()->route('app.payment.error');
}
return view('app.payment.success', compact('payment'));
}
}
I am a bit confused about my folder structure for the scraping code. Using console/commands, not the controller. So, in the handle function I am writing the whole scraping code. But should I suppose to do that? Or... what is the best approach for this?
UPDATED
If I understand correctly the answer below. It should look like this right now.
calling services
class siteControl extends Command
{
protected $signature = 'bot:scrape {website_id}';
protected $description = 'target a portal site and scrape';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$website_id = $this->argument("website_id");
if ($website_id == 1) {
$portal = "App\Services\Site1";
}
$crawler = new $portal;
$crawler->run();
}
}
in handle method
class Site1 extends Utility
{
public function __construct()
{
parent::__construct();
}
public function run()
{
echo "method runs";
}
}
abstract:
use Goutte\Client;
abstract class Utility implements SiteInterfaces
{
protected $client;
public function __construct()
{
$this->client = new Client();
}
}
interfaces:
namespace App\Services;
interface SiteInterfaces
{
public function run();
}
and finally, I should write the whole scraping code inside the run() method? Please correct me If wrong about this... I am searching the best solution.
A best practice would be to call a separate service from your command handle() method. That way you could reuse that same service in a controller for instance.
The technical version:
Your application is given a specific thing to do (a command if you will). This command comes from outside of your application, which can be a anything from a web controller, to an API controller or a CLI application. In terms of hexagonal architecture this is called a port.
Once the application receives such a command it should not care which port it came from. By handling all similar commands in a single spot (a command handler) it does not have to worry about the origins of the command.
So to give you a short overview:
[Web request] [CLI command] <-- these are ports
\ /
\ /
\ /
[Command] <--- this is a method call to your service
|
|
|
[Command handler] <--- this is the service doing the actual work
Updated my answer
Based on the code you provided I implemented what I mentioned above like so:
app/Console/Command/BotScrapeCommand.php
This is the CLI command I mentioned above. All this class has to do is:
1. Gather input arguments; (website_id) in this case
2. Wrap those arguments in a command
3. Fire off the command using the command handler
namespace App\Console\Commands;
use App\Command\ScrapePortalSiteCommand;
use CommandHandler\ScrapePortalSiteCommandHandler;
class BotScrapeCommand extends Command
{
protected $signature = 'bot:scrape {website_id}';
protected $description = 'target a portal site and scrape';
public function handle(ScrapePortalSiteCommandHandler $handler)
{
$portalSiteId = $this->argument("website_id");
$command = new ScrapePortalSiteCommand($portalSiteId);
$handler->handle($command);
}
}
app/Command/ScapePortalSiteCommand.php
This is the Command I mentioned above. Its job is to wrap all input arguments in a class, which can be used by a command handler.
namespace App\Command;
class ScrapePortalSiteCommand
{
/**
* #var int
*/
private $portalSiteId;
public function __construct(int $portalSiteId)
{
$this->portalSiteId = $portalSiteId;
}
public function getPortalSiteId(): int
{
return $this->portalSiteId;
}
}
app/CommandHandler/ScrapePortalSiteCommandHandler.php
The command handler should implement logic based on its command. In this case that's figuring out which crawler to pick, then fire that one off.
namespace App\CommandHandler;
use App\Command\ScrapePortalSiteCommand;
use App\Crawler\PortalSite1Crawler;
use App\Crawler\PortalSiteCrawlerInterface;
use InvalidArgumentException;
class ScrapePortalSiteCommandHandler
{
public function handle(ScrapePortalSiteCommand $command): void
{
$crawler = $this->getCrawlerForPortalSite($command->getPortalSiteId());
$crawler->crawl();
}
private function getCrawlerForPortalSite(int $portalSiteId): PortalSiteCrawlerInterface {
switch ($portalSiteId) {
case 1:
return new PortalSite1Crawler();
default:
throw new InvalidArgumentException(
sprintf('No crawler configured for portal site with id "%s"', $portalSiteId)
);
}
}
}
app/Crawler/PortalSiteCrawlerInterface.php
This interface is there to make sure all crawlers can be called in similar fashion. Additionally it makes for nice type hinting.
namespace App\Crawler;
interface PortalSiteCrawlerInterface
{
public function crawl(): void;
}
app/Crawler/PortalSite1Crawler.php
This is where the implementation of the actual scraping goes.
namespace App\Crawler;
class PortalSite1Crawler implements PortalSiteCrawlerInterface
{
public function crawl(): void
{
// Crawl your site here
}
}
Another update
As you had some additional questions I've updated my answer once more.
:void
The use of : void in a method declaration means the method will not return anything. In a same way public function getPortalSiteId(): int means this method will always return an integer. The use of return typehints was added to PHP 7 and is not specific to Laravel. More information on return typehints can be found in the PHP documentation.
Commands and handlers
The use of commands and command handlers is a best practice which is part of the command bus pattern. This pattern describes an universal way of dealing with user input (a command). This post offers a nice explanation on commands and handlers. Additionally, this blog post describes in more details what a command bus is, how it's used and what the advantages are. Please note that in the code I've provided the bus implementation itself is skipped. In my opinion you do not need it per se, but in some cases it does add value.
I am going through an online course on Laravel. This course is using the League\commonmark package for converting markdown to html.
Whenever the package is used in the app, I get:
Unable to find corresponding renderer for block type League\CommonMark\Block\Element\Document
The app uses the following presenter to do the conversion.
class PagePresenter extends AbstractPresenter
{
protected $markdown;
public function __construct($object, CommonMarkConverter $markdown)
{
$this->markdown = $markdown;
parent::__construct($object);
}
public function contentHtml()
{
return $this->markdown->convertToHtml($this->content);
}
}
Can anyone point me in the right direction?
That happens because the IoC is resolving the dependencies for CommonMarkConverter, specifically Environment which is instantiated with all null properties.
You can probably resolve this by using a Laravel specific integration: https://github.com/GrahamCampbell/Laravel-Markdown
Or you can bind and instance to the service container this way:
In your AppServiceProvider, register method add this:
$this->app->singleton('Markdown', function ($app) {
// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
$environment = \League\CommonMark\Environment::createCommonMarkEnvironment();
// Define your configuration:
$config = ['html_input' => 'escape'];
// Create the converter
return new \League\CommonMark\CommonMarkConverter($config, $environment);
});
Now remove CommonMarkConverter from your Presenter constructor add use app('Markdown'):
class PagePresenter extends AbstractPresenter {
protected $markdown;
public function __construct($object)
{
$this->markdown = app('Markdown');
parent::__construct($object);
}
public function contentHtml()
{
return $this->markdown->convertToHtml($this->content);
}
}
You just put a line in the config/app.php file
'Markdown' => GrahamCampbell\Markdown\Facades\Markdown::class,
I'm trying to register events from within a submodule in Yii.
It just doesn't seem to work.
The init method is definitely called.
class TestModule extends CWebModule
{
public function init()
{
$this->setImport(array(
'test.models.*',
'test.components.*',
));
Yii::app()->onBeginRequest = array($this, 'onBeginRequest');
}
public function onBeginRequest($event) {
die('Request!');
}
public function beforeControllerAction($controller, $action)
{
if (parent::beforeControllerAction($controller, $action))
{
return true;
}
else
return false;
}
}
To register an event you can do:
$this->getEventHandlers($eventName)->add($eventHandler);
Where $eventHandler is the name of the callback you want to define for the $eventName event.
You can also do it with the following way:
$this->attachEventHandler($eventName, $eventHandler);
I solved the problem myself.
The problem was, that i was actually too late for onBeginRequest (Request was alrdy processed).
So what i did was writing a component with Event Handlers for onBeginRequest and onEndRequest, registering the event handlers in config/main.php and call my Module from this Component.
I basically had to proxy all these events.