1. Reading

  • Horstmann, Ed 11, chapter 7, Exceptions

2. Testing exceptions

Exceptions can occur anywhere but most of the time when calling a method. When testing code that throws or should throw exceptions, we can distinguish three cases, each with a different approach. Assume in all cases that the method is defined with a throws clause, as in void read( int mount ) throws IOException.

  1. The exception should not be thrown.

  2. The exceptions should be thrown.

  3. The exception is thrown, but unexpectedly.

To test these cases we use two distinct approaches, covering the first two cases. The last case is automatically dealt with by the JUnit test framework, because any exception that occurs is considered a failure. If unexpected by the test then it is typically the test’s problem.

2.1. Exception is not expected: forward it.

Simply forward the exception. Remember that a (test) method signature is only name and parameter types, not throws clause. So if you do not expect the checked exception to occur in a test method, DO NOT deal with it, but forward it.

Forward exception by having a throws clause
@Test
void testRead() throws IOException {
  String s = this.reader.read();
  assertThat(s).isEqualTo(...);
}

2.2. Exception is expected to be thrown

The test method sets up the tests so that the exception should occur. You want to make sure that the exact proper method call does the throwing, not any expression before or after the 'culprit'. In that case you wrap the expression as a lambda expression of type ThrowingCallable.

Method definition of throwing callable.
  void call() throws Throwable;
Lambda expersion with shape of ThrowingCallable
   () -> {ExceptionThrowingCode}
The trivial but instructive example from the AssertJ assertion framework.
   assertThatThrownBy(() -> { throw new IOException("boom!") }, "Test explosive code") (1)
           .isInstanceOf(IOException.class) (2)
           .hasMessageContaining("boom"); (3)
1 This is of course not a real test, because the code in the lambda throws the exception by itself.
Instead imagine that a method is called or some other expression is evaluated that throws the expected exception
2 Assert that the proper exception is caught and thus thrown.
3 Assert that the message has the correct info. For further details see assertThatThrownBy API doc

2.3. Exception occurs unexpectedly

Well, if you did not expect the exception, the only way to go about is to revise your test code, because there may be something wrong in the test setup. You might need another test to assert that an exception is not thrown. There is a construct for exactly that too.

Make sure the ThrowingCallable is NOT the culprit
   ThrowingCallable doNothing = () -> {}; (1)
   assertThatCode(doNothing).doesNotThrowAnyException();
1 replace lambda by actual suspect code.

3. Slides and lesson demos

4. Exercises week 3

Exercise 1: What is wrong with this exception code

Something Broken in the Land of Exceptions

Read The Fine Compiler Messages

What is the effect of the code below? It may be wrong.

  1. Explain what will happen.

  2. Feed it to the compiler, e.g. by using NetBeans-IDE and see what the compiler thinks of it. Explain any messages you may get.

This is an exercise in RTFCM (Read The Fine Compiler Messages).

Do not repair the code if it appears broken.
Something with Exceptions
class X {
  void m(){
    Object e = new RuntimeException("lets rock the world");
    throw e;
  }
}
Exercise 2: Another wrong with this exception code

More Broken Exceptions

What is the effect of the code below? It may be wrong.

  1. Explain what happens what will happen.

  2. Feed it to the compiler, e.g. by using NetBeans and see what the compiler thinks of it. Explain any messages you may get.

This is an exercise in RTFCM (Read The Fine Compiler Messages).

Do not repair the code if it appears broken.
something with Exceptions
class Y {
  void m(){
    Exception e = new Exception("got you this time");
    try {
      throw e;
    } catch( e ) {
      System.out.println(e.getMessage());
      throw e;
    }
  }
}

Exercise 3: In the pub (with exceptions)

In the pub

For this exercise, you are expanding on the exercise from last week. Develop your own pub simulation, taking possible exceptions into account. Declare your own exceptions and consider if they should be checked (the application/person/role knows how to deal with such exception) or unchecked. Work test driven!

You’ll find the project template in your repository. Note that you not only have to throw exceptions, but must also TEST that they are thrown for the right reasons. When you open the project, the compiler will highlight some errors. Start with implementing the actual Exception classes, which are currently empty. Solve all compilation errors first and then start working test-driven.

These are the specifications for the exceptions of this exercise:

  1. The pub can run out of beer. If so, it should re-stock beer.

  2. A beer drinker can be full and cannot consume any more beer.

  3. Beer consumption is not allowed for people under the age of 18.

Think about what these rules mean for your Pub Simulation. Drinkers will definitely need an age so that you can throw an exception when the age is not 18 or higher.

Exercise 4: Password validator

4.1. Password Validator

Whenever you register an account somewhere, you are asked to create a password. Usually, this password needs to adhere to a certain standard such as a minimum of eight characters or at least one uppercase letter, a special character such as ampersand (&), star (*), dash (-) or exclamation marks (!).

In this exercise, you have to write methods that validate a password. For example, one method checks whether the password contains an uppercase letter. Remember to start writing the tests first! The password should follow these rules:

  • be at least 10 characters long

  • contain at least one uppercase character

  • contain at least one lowercase character

  • contain at least one digit

  • contain at least one special character

  • must not be empty

The java Character class has quite a few static methods to check if a character classifies as a certain kind of character. For instance Character.isDigit('9') will return true as will Character.isUpper('Ä').

There is also the static method int getType(int codePoint), which may be helpful to ease your work. Make a map of Integer to AtomicInteger and count the types that a password has. The types that you are after are UPPERCASE_LETTER, LOWERCASE_LETTER, DECIMAL_DIGIT_NUMBER, and OTHER_PUNCTUATION, END_PUNCTUATION, CURRENCY_SYMBOL. So after you have put all your chars in the map, Check the counts for each category. Upper and, lower and digit need to be at least one, and all the other characters can be counted as special. See the Java doc for more information. Remember, learning to really on being able to browse efficiently through javadoc will be very beneficial during the performance assessment, because it will be available there too.

When the password is empty, you should throw an IllegalArgumentException. Make sure that your tests test that this exception is thrown.

Instead of an IllegalArgumentException, create your own exception and have it thrown and tested. For example a MissingDigitException or a PasswordMustNotBeEmptyException.

Use a tabular test (CsvSource in Junit 5) to make up your test data. Make sure you have a csv test line for each of the failure cases.

4.2. Test run reports Week 3