phpunit expectException() wrong exception name - exception

When I run PHPUnit 6.5.13. and have a test method following this example PHPUnit Testing Exceptions Documentation
public function testSetRowNumberException()
{
$this->expectException(\InvalidArgumentException::class);
$result = $this->tableCell->setRowNumber('text');
}
that tests this method:
public function setRowNumber(int $number) : TableCell
{
if (!is_int($number)) {
throw new \InvalidArgumentException('Input must be an int.');
}
$this->rowNumber = $number;
return $this;
}
I got this failure:
Failed asserting that exception of type "TypeError" matches expected exception "InvalidArgumentException".
the question is why "TypeError" is taken to assertion and how to make assertion use InvalidArgumentException?

Got it. The thing is I used typing set to int that's why the code didn't even reach the thow command.
it works if tested method is without set typing to int:
public function setRowNumber($number) : TableCell
{
if (!is_int($number)) {
throw new \InvalidArgumentException('Input must be an int.');
}
$this->rowNumber = $number;
return $this;
}
or when the test has TypeError
public function testSetRowNumberException()
{
$this->expectException(\TypeError::class);
$result = $this->tableCell->setRowNumber('text');
}
I'll stay with the second example.

Related

Spring WebFlux; unit testing exception thrown in Mono.map()

I have some code that returns Mono<List<UserObject>>. The first thing I want to do is check the List is not empty, and if it is, throw a NoUsersFoundException. My code looks like this:
IUserDao.java
Mono<List<UserAccount>> getUserProfiles(final Set<UserQueryFilter> filters,
final Set<String> attributes);
GetUserAccount.java
public Mono<UserAccount> doGetUserAccount() {
return userDao.getUserProfiles(filters, attributes)
.map(list -> {
if (CollectionUtils.isEmpty(list)) {
throw new NoUsersFoundException();
}
return list;
})
.map(this::removePermissions)
.map(this::removeDuplicates);
}
I want to write a unit test that will test that the NoUsersFoundException is thrown when userDao.getUserProfiles(filters, attributes) returns an empty list. When I use Mockito#when with a .thenReturn(), the test will, as expected, return immediately once userDao.getUserProfiles(...) is called without continuing the flow into the .map() where the list is checked and exception thrown.
#Mock
private IUserDao userDao;
private UserPolicies userPolicies;
#BeforeEach
public void init() {
userPolicies = new UserPolicies(Set.of("XYZ", USER_AFF, "123"),
Set.of(TestUserConstants.ID, TestUserConstants.SUBSCRIPTION_LEVEL));
}
#Test
void shouldThrowExceptionIfNoUsersFound() {
final Set<UserFilter> filters = new UserFilterBuilder().withId(ID)
.withSubscription(PREMIUM)
.build();
when(userDao.getUserProfiles(filters, userPolicies.getUserAttributeIds()))
.thenReturn(Mono.just(Collections.emptyList()));
testClass = new GetUserAccount(userDao,
userPolicies,
filters,
userPolicies.getUserAttributeIds());
assertThatThrownBy(() -> testClass.doGetUserAccount()).isInstanceOf(NoUsersFoundException.class);
}
I have tried .thenAnswer() but it essentially does the same thing as the method called is not a void:
userDao.getUserProfiles(filters, userPolicies.getUserAttributeIds()))
.thenAnswer((Answer<Mono<List>>) invocationOnMock -> Mono.just(Collections.emptyList()));
I can't see how using reactor.test.StepVerifier would work for this case.
i dont really understand what you are asking for, but we commonly dont "throw" exceptions in reactor. We return a Mono#error downstream, and different operators will react accordingly as the error travels downstream.
public Mono<List<Foobar> fooBar(filters, attributes) {
return daoObject.getUserProfiles(filters, attributes)
.map(list -> {
if (CollectionUtils.isEmpty(list)) {
// Return a mono#error
return Mono.error( ... );
}
return list;
})
}
And then test using the step verifier. With either expectNext or expectError.
// Happy case
StepVerifier.create(
fooBar(filters, attributes))
.expectNext( ... )
.verify();
// Sad case
StepVerifier.create(
fooBar(filters, attributes))
.expectError( ... )
.verify();

Conditionally skip a Junit 5 test

In my Junit Jupiter API 5.5 test, I am calling my method which internally makes a HTTP call to a remote service.
Now the remote service can be down or behave incorrectly. I want to skip my test in case the remote service is not behaving expectedly.
#Test
void testMe() {
// do something
Result res1 = myObject.retrieveResults(params)
// assert something
Result res2 = myObject.retrieveResults(param2)
//asert on results
}
Result retrieveResults(Parameters param) {
// do something
// call to remote service
// if they do not give result throw CustomException()
// return Result
}
So basically in my test i would want to check if myObject.retrieveResult is throwing CustomException then skip that test, otherwise evaluate normally.
We have 2 different ways to accomplish this tasks in JUnit 5.
For demo purposes, I have created a basic class which sends a request to the url
that is passed as an argument to its call(String url) method and
returns true or false depending on the request result.
The body of the method is irrelevant here.
Using Assumptions.assumeTrue()/assumeFalse() methods
Assumptions class provides us with two overloaded methods - assumeTrue
and assumeFalse. The idea is that, if the assumption is wrong, the test will be skipped.
So, the test will be something like this.
#Test
void call1() {
Assumptions.assumeTrue(new EndpointChecker(), "Endpoint is not available");
Assertions.assertTrue(HttpCaller.call("https://www.google.com"));
}
Here is the code for EndpointChecker class.
static class EndpointChecker implements BooleanSupplier {
#Override
public boolean getAsBoolean() {
// check the endpoint here and return either true or false
return false;
}
}
When the test is run, the availability of the endpoint will be checked first, if it is up, then the test will run.
Using JUnit 5 extension mechanisms.
So, let's start with creating the annotation. It is pretty straightforward.
#Retention(RetentionPolicy.RUNTIME)
#ExtendWith(EndpointAvailabilityCondition.class)
public #interface SkipWhenEndpointUnavailable {
String uri();
}
And EndpointAvailabilityCondition class. Even though, it looks big, overall logic is very simple.
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation;
public class EndpointAvailabilityCondition implements ExecutionCondition {
#Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
final var optional = findAnnotation(context.getElement(), SkipWhenEndpointUnavailable.class);
if (optional.isPresent()) {
final SkipWhenEndpointUnavailable annotation = optional.get();
final String uri = annotation.uri();
// check connection here start
boolean result = false; // dummy value
// check connection here end
if (result) {
return ConditionEvaluationResult.enabled("Connection is up");
} else {
return ConditionEvaluationResult.disabled("Connection is down");
}
}
return ConditionEvaluationResult.enabled("No assumptions, moving on...");
}
}
Hence, we can do the following in our tests.
#Test
#SkipWhenEndpointUnavailable(uri = "https://www.google.com")
void call2() {
Assertions.assertTrue(HttpCaller.call("https://www.google.com"));
}
We can go ahead and add #Test annotation over #SkipWhenEndpointUnavailable and remove it from our test code. Like, so:
#Retention(RetentionPolicy.RUNTIME)
#ExtendWith(EndpointAvailabilityCondition.class)
#Test
public #interface SkipWhenEndpointUnavailable {
String uri();
}
class HttpCallerTest {
#SkipWhenEndpointUnavailable(uri = "https://www.google.com")
void call2() {
Assertions.assertTrue(HttpCaller.call("https://www.google.com"));
}
}
I hope it helps.

How ro fix "java.lang.AssertionError: expected:<String> but was:<null> "?

I'm trying to test my service and an some point I received an error when I did the assertEquals
This is my test
#Test
public void createNewCommentCreatesNewDTOIfNoDTOExists() {
CommentDTO commentDTO = mock(CommentDTO.class);
MergedScopeKey mergedScopeKey = mock(MergedScopeKey.class);
//set merged scope key
sut.setInput(mergedScopeKey);
String commentText = "commentText";
//define behaviour
when(commentApplicationService.createCommentDTO(mergedScopeKey, commentText)).thenReturn(commentDTO);
sut.createNewComment(commentText);
//test the functionality
assertNotNull(commentDTO);
assertEquals(commentText, commentDTO.getCommentText());
//test the behavior
verify(commentApplicationService).createCommentDTO(mergedScopeKey, commentText);
}
And this is my method that I wanted to test:
protected void createNewComment(String commentText) {
CommentDTO commentDTO = commentApplicationService.getDTOComment(mergedScopeKey);
if (commentDTO == null) {
commentApplicationService.createCommentDTO(mergedScopeKey, commentText);
} else {
updateComment(commentDTO, commentText);
}
}
Do you have any ideas what I do wrong ?
You define behaviour:
when(commentApplicationService.createCommentDTO(mergedScopeKey, commentText)).thenReturn(commentDTO);
But in your test you call:
CommentDTO commentDTO = commentApplicationService.getDTOComment(mergedScopeKey);
This is a different method, you receive null here.
Even if you fix this, you call updateComment. It is highly unlikely that your production code sets expectations on the passed in mock, thus you will always receive null from commentDto.getCommentText()
Consider using a real class instead of a mock for DTO classes.

expectException doesn't detect the exception

I am trying to test database records retrieving. My test looks like:
use yii\db\Exception;
class UserTest extends Unit
{
protected $tester;
private $_user;
public function _before()
{
$this->_user = new User();
}
public function testRetrievingFALSE()
{
$this->expectException(Exception::class, function(){
$this->_user->retrieveRecords();
});
}
}
I saw the expectException() method in the documentation. My model method looks like this:
public function retrieveRecords()
{
$out = ArrayHelper::map(User::find()->all(), 'id', 'username');
if($out)
return $out;
else
throw new Exception('No records', 'Still no records in db');
}
What am I doing wrong in this scenario?
In terminal:
Frontend\tests.unit Tests (1) ------------------------------------------------------------------------------------------
x UserTest: Retrieving false (0.02s)
------------------------------------------------------------------------------------------------------------------------
Time: 532 ms, Memory: 10.00MB
There was 1 failure:
---------
1) UserTest: Retrieving false
Test tests\unit\models\UserTest.php:testRetrievingFALSE
Failed asserting that exception of type "yii\db\Exception" is thrown.
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
You're not using the same method which you're referring to. You should use it on actor instance, not on unit test class itself. So either:
$this->tester->expectException(Exception::class, function(){
$this->_user->retrieveRecords();
});
Or in acceptance tests:
public function testRetrievingFALSE(AcceptanceTester $I) {
$I->expectException(Exception::class, function(){
$this->_user->retrieveRecords();
});
}
If you call it on $this in test class, method from PHPUnit will be used, which works differently:
public function testRetrievingFALSE() {
$this->expectException(Exception::class);
$this->_user->retrieveRecords();
}
See more examples in PHPUnit documentation.

Catch exception from controller in middleware

I have a laravel controller that can throw an exception, and a global middleware that catches that exception. In semi pseudo code:
// App\Controllers\...
class Controller {
function store() {
throw new FormException; // via validation etc, but it's thrown here
}
}
// App\Http\Middleware\...
class Middleware {
function handle(Closure $next) {
try {
// Breakpoint 1
return $next(); // $response
// Breakpoint 2
}
catch (FormException $ex) {
// Breakpoint 3
exit('FormException caught!');
}
}
}
The problem is that the exception is never caught. Somwhere in the pipeline, the application catches the exception and prints a pretty error page, but it should be caught by my middleware so it can handle it properly.
Breakpoint 1 should trigger, and it does << good
Breakpoint 2 shouldn't trigger, and it doesn't << good
Breakpoint 3 should trigger, but it doesn't << what??
The only way I can imagine my middleware not catching it, is if it's caught somewhere deeper inside the pipeline, not further up/around, but I can't find any try/catch in other middleware, or in the pipeline execution code.
Where is this exception caught? Why?
This might not be a great pattern, but I don't care about that now. I'm more curious than anything else. Do I completely misunderstand Laravel's middleware?
My own super simple middleware test does what I expected: https://3v4l.org/Udr84 - catch and handle exception inside middleware.
Notes:
The $response object (return value of $next()) is the handled exception page, so it has already been handled. Where and why?
Handling the exception in App\Exceptions\Handler::render() works, but I want all logic to be in a middleware package, not in app code.
Relevant Laravel code:
Kernel::handle() starts the middleware pipeline << this has a catch-all catch(), but my catch() comes first, right?
Pipeline::then() starts the middleware execution
Pipeline::getSlice() handles and creates the $next closures
Apparently this is by design:
Yes, this is the beavhiour starting from L5.2. Throwing an exception causes the response to be set as that returned from the exception handler, and then the middleware is allowed to backout from that point.
I think that's very strange. Pluggable middleware would be perfect for catching exceptions.
Two ways to still do this:
Proper: in App\Exceptions\Handler, which is not good enough, because a package can't touch that
Funky: take the original exception object from the response object:
$response = $next($request);
$exception = $response->exception;
I had the same problem. When reading the thread Rudie mentioned, they give a possible solution there which worked for me:
public function handle(Request $request, Closure $next) {
$response = $next($request);
// 'Catch' our FormValidationException and redirect back.
if (!empty($response->exception) && $response->exception instanceof FormValidationException) {
return redirect()->back()->withErrors($response->exception->form->getErrors())->withInput();
}
return $response;
}
Looking at the source code, you need to catch both \Exception and \Throwable for your try catch to properly work in your middleware. This works on Laravel 5.8
class TryCatchMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
try {
if ( somethingThatCouldThrowAnException() ) {
$request->newVariable = true;
}
} catch (\Exception $e) {
// do nothing
} catch (\Throwable $e) {
// do nothing
}
return $next($request);
}
}
How catch errors without touching App\Exceptions\Handler file:
Register your CustomExceptionHandler
/* #var ExceptionHandler Illuminate\Contracts\Debug\ExceptionHandler */
$previousHandler = null;
if (app()->bound(ExceptionHandler::class) === true) {
$previousHandler = app()->make(ExceptionHandler::class);
}
app()->singleton(ExceptionHandler::class, function () use ($previousHandler) {
return new CustomExceptionHandler($previousHandler);
});
And your basic CustomExceptionHandler
class CustomExceptionHandler implements ExceptionHandlerInterface
{
/**
* #var ExceptionHandlerInterface|null
*/
private $previous;
public function __construct(ExceptionHandlerInterface $previous = null)
{
$this->previous = $previous;
}
public function report(Exception $exception)
{
$this->previous === null ?: $this->previous->report($exception);
}
public function render($request, Exception $exception)
{
if ($exception instanceof CustomExceptionHandler) {
echo 'This is my particular way to show my errors';
} else {
$response = $this->previous === null ? null : $this->previous->render($request, $exception);
}
return $response;
}
/**
* {#inheritdoc}
*/
public function renderForConsole($output, Exception $exception)
{
/* #var OutputInterface $output */
$this->previous === null ?: $this->previous->renderForConsole($output, $exception);
}
}
I think I can see why your code doesn't catch exceptions. Please try using the following code for your handle method:
function handle(Closure $next) {
try {
// Breakpoint 1
$response = $next();
// Breakpoint 2
}
catch (FormException $ex) {
// Breakpoint 3
exit('FormException caught!');
}
return $response;
}
The code above has not been tested but as you look at the Laravel documentation you can see before returning the response, you should perform your code (in this case, your exception handling logic). Please look at: Laravel - Defining Middleware for more information on Before & After Middleware definition.
And by the way, also look at this file: Laravel/app/Exceptions/Handler.php which I believe is a better place to handle your exceptions globally.