phpspec test logic in constructor - phpspec

I have a logic like this
public function __construct(DataFetcherInterface $fetcher, Alert $alert) {
$this->data = $fetcher->getData();
foreach($this->data as $info) {
try {
$fetcher->assoicateAddress($info);
} catch (Exception $e) {
$alert->sendAlert($info)
}
}
}
and I want to test logic here, for example if sendAlert() will be called in case of exception.
public function it_should_throw_exception(
DataFetcherInterface $fetcher,
Alert $alert
): void {
$fetcher->getData()->willReturn(
// data here
)->shouldBeCalled();
$fetcher->assoicateAddress(
// data here
)->willThrow(Excpetion::class)->shouldBeCalled();
$alert->sendAlert()->shouldBeCalled(
// data here
);
$this->beConstructedWith($fetcher, $logger); // <== not sure how to do it here
}
and it's not working, error:
- it should throw exception
some predictions failed:
DataFetcherInterface\P3:
No calls have been made that match:
DataFetcherInterface\P3->getData()
but expected at least one.
I know php spec is for TDD, and I should start from tests, but at the moment I start adding tests I already had some come, for new code I'll use TDD approach.
Is it a bad practice to have such logic in constructor?? I have other methods in the class as well, but I want it to be instantiated with all needed data, and I'm using Visitor pattern to get all data while initializing object

Related

Handle Exception From Within Method

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'));
}
}

How to mock a Job object in Laravel?

When it comes to Queue testing in Laravel, I use the provided Queue Fake functionality. However, there is a case where I need to create a Mock for a Job class.
I have the following code that pushes a job to a Redis powered queue:
$apiRequest->storeRequestedData($requestedData); // a db model
// try-catch block in case the Redis server is down
try {
App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run');
$apiRequest->markJobQueued();
} catch (\Exception $e) {
//handle the case when the job is not pushed to the queue
}
I need to be able to test the code in the catch block. Because of that, I'm trying to mock the Job object in order to be able to create a faker that will throw an exception.
I tried this in my Unit test:
ProcessRunEndpoint::shouldReceive('dispatch');
That code returns: Error: Call to undefined method App\Jobs\ProcessRunEndpoint::shouldReceive().
I also tried to swap the job instance with a mock object using $this->instance() but it didn't work as well.
That said, how can I test the code in the catch block?
According to the docs, you should be able to test jobs through the mocks provided for the Queue.
Queue::assertNothingPushed();
// $apiRequest->storeRequestedData($requestedData);
// Use assertPushedOn($queue, $job, $callback = null) to test your try catch
Queue::assertPushedOn('run', App\Jobs\ProcessRunEndpoint::class, function ($job) {
// return true or false depending on $job to pass or fail your assertion
});
Making the line App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run'); throw an exception is a bit complicated. dispatch() just returns an object and onQueue() is just a setter. No other logic is done there. Instead, we can make everything fail by messing with the configuration.
Instead of Queue::fake();, override default queue driver with one that just won't work: Queue::setDefaultDriver('this-driver-does-not-exist'); This will make every job dispatch fail and throw an ErrorException.
Minimalist example:
Route::get('/', function () {
try {
// Job does nothing, the handle method is just sleep(5);
AJob::dispatch();
return view('noError');
} catch (\Exception $e) {
return view('jobError');
}
});
namespace Tests\Feature;
use App\Jobs\AJob;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
class AJobTest extends TestCase
{
/**
* #test
*/
public function AJobIsDispatched()
{
Queue::fake();
$response = $this->get('/');
Queue::assertPushed(AJob::class);
$response->assertViewIs('noError');
}
/**
* #test
*/
public function AJobIsNotDispatched()
{
Queue::setDefaultDriver('this-driver-does-not-exist');
$response = $this->get('/');
$response->assertViewIs('jobError');
}
}
I found a solution. Instead of using a facade for adding a job to the queue (App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run');), I injected it into the action of the controller:
public function store(ProcessRunEndpoint $processRunEndpoint)
{
// try-catch block in case the Redis server is down
try {
$processRunEndpoint::dispatch($apiRequest)->onQueue('run');
} catch (\Exception $e) {
//handle the case when the job is not pushed to the queue
}
}
With this, the job object is resolved from the container, so it can be mocked:
$this->mock(ProcessRunEndpoint::class, function ($mock) {
$mock->shouldReceive('dispatch')
->once()
->andThrow(new \Exception());
});
Although still not sure why the shouldReceive approach doesn't work for the facade: https://laravel.com/docs/8.x/mocking#mocking-facades

Throwing a \Doctrine\DBAL\Driver\DriverException in a unit test mock

Redacting unit tests, I am confronted to this problem. A piece of code that I want to test catches a \Doctrine\DBAL\Exception\RetryableException. The first constructor in the classes chain is the one of DriverException and is built like this :
/**
* #param string $message The exception message.
* #param \Doctrine\DBAL\Driver\DriverException $driverException The DBAL driver exception to chain.
*/
public function __construct($message, \Doctrine\DBAL\Driver\DriverException $driverException)
{
$exception = null;
if ($driverException instanceof Exception) {
$exception = $driverException;
}
parent::__construct($message, 0, $exception);
$this->driverException = $driverException;
}
I feel like I am confronted to the problem of the egg and the chicken, here. How can I instanciante a class that takes an instance of itself as mandatory argument in the first place ?
Note: I won't mark this auto-response as a solution, it is more a workaround.
Instead of throwing the right exception in my unit test mock, I have created a simpler one, extending Exception but still implementing the original interface RetryableException, as it's the interface that is caught in the code I am testing. While not being what I wanted to do, it does the job in my precise case.
Here is how I have an actual instance of DriverException in my unit tests, using an anonymous class instead of a mock:
<?php
declare(strict_types=1);
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Exception\DriverException;
use PHPUnit\Framework\TestCase;
final class MyTest extends TestCase
{
// ... the rest of the test case
private function getDriverExceptionWithCode(int $code): DriverException
{
$theDriverException = new class($code) extends \Exception implements TheDriverException {
public function __construct(int $code)
{
parent::__construct('oh no, you broke it :(', $code);
}
public function getSQLState(): ?string
{
return null;
}
};
return new DriverException($theDriverException, null);
}
}
In my case I needed to unit test a situation where the code is catching a DriverException with a specific code, but you can extend the code as you wish, or make it simpler. The only thing you need is to implement getSQLState, after all.
Hope this helps whoever stumbles on this question from their favorite search engine.

Spring aspect for crosscutting process pipeline usage

I want to use spring aspect for my crosscutting process like a before or after handler in my process execution lifetime.
For example
What I want before a method I want to execute my handler and due to its response I want to finialize myprocess and return custom response.
In example in stop condition should I throw custom exception to stop process that time how can I handle my return response ,I want to give meaningfull object at the client.What is the best way to do that?
#Before
MyHandler()
{
bool stop=checkvalue();
if(stop==false)
continue...
else
{
//break all process
and return custom response to client
//throw exception but meaningfullresponse??
}
}
Instead of using Before advice, I would use Around advice to wrap the method invocation and call checkValue() on beforehand:
#Around("someJoinpoint")
public Object MyHandler(ProceedingJoinPoint pjp) {
if (!checkvalue()) {
return pjp.proceed();
} else {
return someCustomResponseToClient();
}
}
More info on Around advice can be found in the Spring documentation.

Should a method should be throwing an exception to the Unit Test?

I have a simple method for sending emails:
public void notifyEmail(string messageSubject, string messageBody)
{
MailMessage message = new MailMessage(from, to);
message.Subject = messageSubject;
message.Body = messageBody;
SmtpClient client = new SmtpClient(smtp_client);
client.Send(message);
message.Dispose();//release everything related
}
And a unit test (I'm learning):
[TestMethod()]
public void notifyEmailTest()
{
eMail target = new eMail("TEST Subject","TEST Body"); // TODO: Initialize to an appropriate value
bool testSent = true;
try
{
target.notifyEmail();
}
catch (Exception)
{
testSent = false;
}
Assert.IsTrue(testSent);
}
I deliberately set the smtp_client variable value to something invalid.
Running the code in my project results in an error.
Running the test method results in a Pass. Should my test or method be structured differently so that errors will fail the test?
I always do everything I can to avoid putting try-catch clauses on my unit tests. Instead try using the ExpectedException attribute (the attribute is the same for NUnit and MSTest) and set the type to the exception you are expecting i.e.
[TestMethod]
[ExpectedException(typeof(NetworkException))]
public void ShouldThrowNetworkExceptionIfSmtpServerIsInvalid)
{
//... test code here.
}
Another approach that I have used is to create a static class with an AssertExpectedException method since sometimes a method can throw the same type of exception for different reasons and the only way to know for sure if the accurate message is being returned is with custom code since the attribute does not assert the message the thrown exception is returning.
Hope this helps.
Regards.
If you expect that target.notifyEmail() should be throwing an exception, then that's what you should be testing for. If you were using NUnit you could use Assert.Throws<T>, e.g.
[Test]
public void notifyEmailTestFails()
{
// TODO: Initialize to an appropriate value
eMail target = new eMail("TEST Subject","TEST Body");
Assert.Throws<InvalidOperationException>(target.notifyEmail());
}
However, now I see you're using VSUnit you should be using [ExpectedException(typeof(...))]
as mentioned in other answers.
In general you should have separate tests for success, failure, and for exception conditions.
The way I normally do this is to decorate the test with ExpectedException (
http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.expectedexceptionattribute(v=vs.80).aspx)
. But you want to catch something MUCH less generic than "Exception."
If you don't want to use expected exception, then instead of:
bool testSent = true;
try
{
target.notifyEmail();
}
catch (Exception)
{
testSent = false;
}
Assert.IsTrue(testSent);
You can be a little less verbose:
try{
target.notifyEmail();
Assert.Fail("Expected an exception here");
}
catch (SmtpException){
}
I would highly recommend you to try the FluenAssertions:
http://fluentassertions.codeplex.com/
They are simple awesome and Elegant
And they let you check the exception message (You can not do that with the ExpectedException attribute)
Example:
using FluentAssertions;
[TestMethod]
public void notifyEmailTest()
{
eMail target = new eMail("TEST Subject","TEST Body"); // TODO: Initialize to an appropriate value
target.Invoking(x => x.notifyEmail())
.ShouldThrow<YourExcpectedException>()
.WithMessage("Your expected message", FluentAssertions.Assertions.ComparisonMode.Substring);
}

Resources