6

I have few custom exceptions in PHP:

class MainException extends Exception {};
class ExceptionOne extends MainException {};
class ExceptionTwo extends MainException {};

And I'm using them in my class in two simple methods:

public function firstFunction($param) {
    if ($some_condition) {
        // do whatever
    } else {
        throw new ExceptionOne();
    }
}

public function secondFunction($param) {
    if ($some_condition) {
        // do whatever
    } else {
        throw new ExceptionTwo();
    }
}

I also have a PHPUnit test for both of exceptions, similar to this:

public function testFirstException() {
    try {
        // anything
    } catch (Exception $e) {
        $this->assertType('ExceptionOne', $e);
        $this->assertType('MainException', $e);
    }
}
public function testSecondException() {
    try {
        // anything
    } catch (Exception $e) {
        $this->assertType('ExceptionTwo', $e);
        $this->assertType('MainException', $e);
    }
}

If I test my class in browser and intentionally make my functions fail (with same stuff as in PHPUnit test) I can see ExceptionOne and ExceptionTwo are raised whenever needed. However, when I test it with PHPUnit I always get a failure:

1) testSecondException(SomeTest)
Failed asserting that <PHPUnit_Framework_ExpectationFailedException> is an instance of class "ExceptionTwo".
C:\test.php:67

Line 67 is
$this->assertType('ExceptionTwo', $e);
and it fails here no matter what I try. I'm pretty sure that my condition in secondFunction works correct. The first test (testFirstException) works perfectly and I never get a failure like in the second one.
I'd like to add that I shouldn't change PHPUnit test here!

What am I doing wrong here??

3 Answers 3

8

From your comments to cbuckley's excellent answer, is appears that the test was written like this:

public function testFirstException() {
    try {
        // some code that is supposed to throw ExceptionOne
        $this->assertTrue(false, "Test failed");
    } catch (Exception $e) {
        $this->assertType('ExceptionOne', $e);
        $this->assertType('MainException', $e);
    }
}

The assertTrue is used to make sure the test fails if the exception isn't thrown during the test. However, this doesn't work because the failed assertion throws a different exception type which causes the error message to be confusing.

Failed asserting that <PHPUnit_Framework_ExpectationFailedException> 
is an instance of class "ExceptionOne".

You can fix this by using either @expectedException or setExpectedException. Not only will the test pass when ExceptionOne is thrown, but it will fail when it isn't thrown or some other exception type is thrown.

/**
 * @expectedException ExceptionOne
 */
public function testFirstException() {
    // some code that is supposed to throw ExceptionOne
}

When no exception is thrown the error message is

Failed asserting that exception of type "ExceptionOne" is thrown.

and when different type of exception is thrown you'll see

Failed asserting that exception of type "Exception" matches
expected exception "RuntimeException".

This method of testing exceptions is

  • easier to write,
  • easier to read,
  • easier to debug when the test fails,
  • and correct.
6
  • Yes, that's how my try block is written in test. Now I'm not sure myself anymore and will triple-check my class again...
    – errata
    Commented Nov 2, 2012 at 16:27
  • GRRRR!!! A stupid typo in my regex pattern was causing this all the time >.-( I feel like a total noob now really :) Anyways, thanks one more time for all this great info about testing exceptions, will keep in mind that in the future for sure!
    – errata
    Commented Nov 2, 2012 at 16:48
  • 1
    I highly recommend that you share this advice with whoever wrote that test. You could have been saved from all this misery. Commented Nov 2, 2012 at 19:40
  • 1
    +1 for making my answer clearer :-) FWIW, I don't recommend using @expectedException, because it only asserts that such an exception is thrown somehwere in the test code. If using setExpectedException, you can precisely control when you expect the exception to be thrown by putting it immediately before the line that's expected to fail.
    – cmbuckley
    Commented Nov 3, 2012 at 14:07
  • Hehe, David, I certainly will do so :) Unfortunately it was the error in my code in the end, and even if I don't have so much experience with PHPUnit, I clearly understand the point here... As Bender would say: Learning is fun! :)
    – errata
    Commented Nov 4, 2012 at 20:57
7

It looks like you're masking a different error inside your try/catch. If there is a test assertion within your try block, and that assertion fails, then it is thrown as an exception. Try using setExpectedException instead of a try/catch block:

public function testFirstException() {
    $this->setExpectedException('ExceptionOne');
    $fixture->firstFunction();
}

You should put the setExpectedException call immediately before the line which you expect to throw the exception.

Another alternative is to use the @expectedException docblock annotation.

/**
 * @expectedException ExceptionOne
 */
public function testFirstException() {
    $fixture->firstFunction();
}

If you want to keep the try/catch approach, the same applies: make sure that the only line in the try block is the line which should throw the exception.

6
  • 2
    Additionally if you use the try/catch approach and are using PHPUnit 3.6 or higher, you should use assertInstanceOf instead of assertType. This is per the phpunit documentation itself as assertType is deprecated.
    – D-Rock
    Commented Nov 2, 2012 at 15:48
  • As I said, I really shouldn't change the test file here... But I'll try to do this and see what will happen...
    – errata
    Commented Nov 2, 2012 at 15:49
  • Hmm... So, in both try blocks in testing functions there are 2 lines: one is calling the method from my class, the other one is $this->assertTrue(false, "Some message");. If I delete the second one from testSecondException, everything works as expected... Still, I think I really shouldn't change the code in testing file for few different reasons... Is there anything else I could try? How comes that testFirstException works fine with 2 lines of code in try block? :/
    – errata
    Commented Nov 2, 2012 at 15:58
  • You need to fix your tests. A failed assertion is marked as such by throwing an exception. In that case, you shouldn't catch generic exceptions. How come you don't want to change your tests? The try/catch approach is causing you a problem. At the very least, update to be like the PHPunit example (i.e. don't catch generic exceptions, and don't do assertTrue(false))!
    – cmbuckley
    Commented Nov 2, 2012 at 16:02
  • I see... Well, thanks a lot for helping! Regarding changing the test itself... I got a task to write that class and to test it with PHPUnit which was already written for me... Nowhere in the task description is stated that "there's something wrong in testing code" so I suppose I shouldn't touch it :)
    – errata
    Commented Nov 2, 2012 at 16:08
0

With php unit you must generate the exception about any of the methods that are within the Try {} falsifying your response.

public function testFirstFunction() {
....
$this->methodOnGenerateExeption(Argumen::Any())->willReturn()->willThrow(\Exception::class);
$this->setExpectedException(\Exception::class);

  $this->classMockToTest->firstFunction($parameters)
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.