Understanding Exception tests in JUnit 5 - java

I've been away from Java for a while and JUnit 5 has come along in the meantime. I'm trying to rewrite some existing JUnit 4 tests as JUnit 5 but I'm struggling with how to handle methods that throw exceptions. For example, I have a class I wrote called LocalizationUtils (not to be confused with any other class by that name found in the API) and it has a method called getResources() which wants a non-null base name as an input and returns the corresponding resource bundle if it can be found. It throws a NullPointerException if the base name is null and a MissingResourceException if the resource bundle can't be located. Here's the code:
static public ResourceBundle getResources(String baseName) throws NullPointerException, MissingResourceException {
if (baseName == null) {
final String msg = "The base name cannot be null.";
NullPointerException npExcp = new NullPointerException(msg);
Logger logger = Logger.getLogger(CLASS_NAME);
logger.log(Level.SEVERE, msg, npExcp);
throw npExcp;
}
/* Get the resource bundle for the current locale. */
try {
return(ResourceBundle.getBundle(baseName));
}
catch (MissingResourceException mrExcp) {
String msg = "Unable to find resources for base name " + baseName + ".";
Logger logger = Logger.getLogger(CLASS_NAME);
logger.log(Level.SEVERE, msg, mrExcp);
throw mrExcp;
}
}
The manual for JUnit 5 gives this example of handling an exception:
#Test
void exceptionTesting() {
Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("a message");
});
assertEquals("a message", exception.getMessage());
}
This example makes no sense to me. It seems to be creating an exception with the message "a message" out of thin air. I don't see that it is actually executing ANY class or method at all. There's no real explanation of how this is supposed to work so maybe/probably it's just a misunderstanding on my part.
In an attempt to write a test that actually executes my code and forces both errors to occur, I wrote this JUnit 5 test:
#Test
void testGetResourcesString() {
//Normal cases
//Exception - input parameter is null
/* Verify that the expected type of exception was thrown. */
Throwable npExcp = assertThrows(NullPointerException.class, () -> {
LocalizationUtils.getResources(null);
});
/* Verify that the exact right error message was generated. */
assertEquals("The base name cannot be null.", npExcp.getMessage());
//Exception - all input is non-null but resource bundle not found
/* Verify that the expected type of exception was thrown. */
Throwable mrExcp = assertThrows(MissingResourceException.class, () -> {
LocalizationUtils.getResources("foo");
});
/* Verify that the exact right error message was generated. */
assertEquals("Can't find bundle for base name foo, locale en_CA", mrExcp.getMessage());
}
Everything in this test works as I would expect and it seems more logical than the example in the manual since it actually executes a class and method to cause the exception to be thrown. I also wanted to be sure that exactly the right message was generated since it's easy to imagine a method with several input parameters instead of just one where any of the parameters being null should throw an exception with a unique message. Simply determining that the right exception was thrown doesn't seem like enough to me: I feel that I want to be sure that the right message was created so that I know the code is reacting to the right null parameter out of several.
I'm reluctant to believe the manual is wrong though; I imagine a team of several experienced developers wrote it and were very careful. Can anyone with more JUnit 5 knowledge than me - which must be just about everyone who has ever used JUnit 5 - confirm that the example in the manual is correct and, if it is, how it is supposed to work when no class or method name is provided?

The example does call a "method". The lambda expression () -> {...} defines an anonymous method. It contains one statement: throw new IllegalArgumentException.
Refer to the java language specs
() -> {} // No parameters; result is void
() -> 42 // No parameters, expression body
() -> null // No parameters, expression body
() -> { return 42; } // No parameters, block body with return
() -> { System.gc(); } // No parameters, void block body
(int x) -> x+1 // Single declared-type parameter
(int x) -> { return x+1; } // Single declared-type parameter
(x) -> x+1 // Single inferred-type parameter
x -> x+1 // Parens optional for single inferred-type case
So the example has one method, and all it does is throw an exception.
() -> { throw new IllegalArgumentException("a message"); }
And in your code, you're actually defining one method with no parameters, calling your method with one parameter.
() -> { LocalizationUtils.getResources(null); }

Related

How do I verify the HTTP status code if it is a subclass of WebApplicationException?

I am trying to test that my controller will indeed throw an error with the response code of 409 conflict. My controller looks like this:
if (Object != null) {
return Object;
} else {
throw new WebApplicationException(Response.Status.CONFLICT);
}
In the unit test I have something like this:
assertThrows(WebApplicationException.class, () -> controller.createObject();
Although I feel like this test isn't comprehensive enough as I haven't verified the response code of the call. How does one do that, if it is possible?
Instead of asserting that an exception is thrown, you need to enclose the code that should throw the exception within a try / catch, catch the exception, then get the response from the exception object and check its status code. Something like this:
try {
controller.createObject();
assertFail("should have thrown an exception");
} catch (WebApplicationClass ex) {
assertEquals(404, ex.getResponse().getStatusCode());
}
assetThrow method description
Assert that execution of the supplied executable throws an exception of the expectedType and return the exception.
If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
If you do not want to perform additional checks on the exception instance, simply ignore the return value.
the return value of the assertThrows method is your exception instance, so you can do this
WebApplicationClass e = assertThrows(HttpException.class, () -> controller.createObject());
// perform additional checks
assertEquals(xxx, e.getResponse().getxxx);

How to continue test after JUnit ExpectedException if thrown?

I have set up some JUnit (4.12) test with the ExpectedException feature, and I would like the test to continue after the expected exception. But I never see the log '3', as the execution seems to stop after the exception, event if catch?
Is this actually possible, and how?
#Rule
public ExpectedException exception = ExpectedException.none();
#Test
public void testUserAlreadyExists() throws Exception {
log.info("1");
// Create some users
userService.createUser("toto1");
userService.createUser("toto2");
userService.createUser("toto3");
Assert.assertTrue( userService.userExists("toto1") );
Assert.assertTrue( userService.userExists("toto2") );
Assert.assertTrue( userService.userExists("toto3") );
log.info("2");
// Try to create an existing user
exception.expect(AlreadyExistsException.class);
userService.createUser("toto1");
log.info("3");
}
You cannot do that, when the exception is thrown it's thrown for real, ExpectedException rule or not.
If you really want this kind of behaviour, you can go back to the "old school" pattern:
try {
userService.createUser("toto1");
Assert.fail("expecting some AlreadyExistsException here")
} catch (AlreadyExistsException e) {
// ignore
}
log.info("3");
But I wouldn't bother for some log.
This SO solution seems to do what you want to do: JUnit continue to assert things after expected exception
I myself was thinking something similar. To continue with the test, you would have to catch the exception yourself in the test. This solution shows an elegant way of doing that.
Note: If you make a rule to expect an exception (as you did), the test will return successful as soon as that exception is thrown.
Reference: http://junit.org/javadoc/latest/org/junit/rules/ExpectedException.html
If you don't want to add a lot of similar test methods for something that has many options to throw the expected exception and want to verify that it actually throws on all of the desired cases within a single unit-test instead, I'd suggest this (not pretty maybe) helpful schema:
#Test
public void testThatSomethingExpectedlyFails() {
for (int i = 1; i <= 3; i++) {
try {
switch (i) {
case 1: // smth here throws the exception when configuration #1;
case 2: // smth here throws the exception when configuration #2;
case 3: // smth here throws the exception when configuration #3;
}
} catch (ExceptionThatIsExpected expected) {
continue;
} catch (Exception unexpected) {
/* the test must fail when an unexpected exception is thrown */
fail("The test has failed due to an unexpected exception: " + unexpected.getMessage()); // or just re-throw this exception
}
/* the test must fail when a case completes without the expected exception */
fail("No expected exception occurred at case " + i);
}
}
The one could also iterate items (and even execute functions) of some preliminarily prepared list instead of switch-case with hard-coded integers.
First of all your test doesn't test one thing. It tests "userExists" and "createUser" under different conditions a.k.a. different scenarios. This is called an AssertionRoulette. You wouldn't need a hack to continue to log "3", if you would write tests, that fail fo the right reason.
If the tests fail for the right reason, you can see the scenario why it fails without doing all the logging stuff. The Junit-Runner does the logging for you already.
#Test
public void testUserExists_UserCreatedUserNotExistent_expectTrue()
{
// Create some users
userService.createUser("toto1");
// Assert That user exists
Assert.assertTrue( userService.userExists("toto1") );
}
#Test
public void testCreateUser_UserAlreadyCreated_expectAlreadyExistsExceptionIsThrown()
{
// Create some users
userService.createUser("toto1");
// Try to create an existing user
exception.expect(AlreadyExistsException.class);
userService.createUser("toto1");
}

How to write an exception factory in Java

We are currently developing a framework for internal use. We are now at the point that we want to use standardized exception IDs and messages. The developer just has to provide an ID and a default message and the framework looks up the associated message(if possible) or uses the default message(if the message could not be retrieved).
The first idea was to write an exception factory which enforces the developer to provide an error ID and then lookup the message in the factory method. The problem with this approach is, that an additional frame is added in the stacktrace(the factory method where the exception is created). Thats not very nice. Almost every solution which tries to hide the creation has this issue.
The second idea was to couple the message lookup and our exception class and write an abstract exception class which takes the error ID in the constructor and handles the message lookup. But thats also not very nice cause it lacks loose coupling.
Third idea was let the developer write the lookup of the message every time.. also bad..
Do you have any ideas?
P.S. I think thats the first time I need macros in Java..
the different approaches in code:
1.
throw createException(new ErrorId("123"), TechnicalException.class, "defaultmsg"); //uses reflection, also not very nice but works
or
throw createTechnicalException(new ErrorId("123", "defaultmsg"); //write a method for every type of exception
2.
public TechnicalException(ErrorId errorId, String defaultmsg) {
super(defaultmsg);
//get msg here and set on a new field cause detailMessage is not accessible. Also overwrite toString();
}
throw new TechnicalException(new ErrorId("123"), "defaultmsg");
3.
ErrorId errorId = new ErrorId("123");
String msg = someProvider.getMessage(errorId);
throw new TechnicalException(errorId, msg);
It's possible to edit the stack trace, removing unneeded elements. If all exceptions' constructors need the same data, use single createException, else use createCustomException methods.
This removes unnecessary stack trace elements.
public static <T extends Throwable> T adjustStackTrace(T ex, int removedElements) {
//use an explicit constraint for removedElements
if (removedElements < 1) {
throw new IllegalArgumentException("removedElements may not be less than 1");
}
final StackTraceElement[] stack = ex.getStackTrace();
//or an implicit constraint
removedElements = Math.max(removedElements, stack.length);
final StackTraceElement[] newStack = new StackTraceElement[stack.length - removedElements];
System.arraycopy(stack, removedElements, newStack, 0, stack.length - removedElements);
ex.setStackTrace(newStack);
return ex;
}
a method for a custom excepton:
public static TechnicalException createTechnicalException(Object... someExceptionSpecificData) {
StringBuilder sb = new StringBuilder();
sb.append("...").append("..."); //create a message
//process the someExceptionSpecificData
Object[] someNewData = someExceptionSpecificData;
TechnicalException ex = new TechnicalException(sb.toString(), someNewData);
ex = adjustStackTrace(ex, 1 /*remove StackTraceElement, related to this method call*/);
return ex;
}
Or you can use a single method and pass desired exception class:
public Exception createException(Class<?> exClass, Object... someExceptionSpecificData) {...}
usage example:
public void useExceptionFactory() {
throw ExceptionFactory.createTechnicalException();
}
public void adjustEvenMore() {
//adjust even more to hide the implementation of your library
throw ExceptionFactory.adjustStackTrace(ExceptionFactory.createTechnicalException(),1);
}
This example is based on net.sf.oval.internal.util.Assert from OVal framework, see http://oval.sourceforge.net/

Accumulating / Collecting Errors via ErrorListener to handle after the Parse

The ErrorListener mechanism in Antlr4 is great for logging and making decisions about syntax errors as they occur during a parse, but it can get better for batch error handling after the parse is finished. There are a number of reasons you might want to handle errors after the parse finishes, including:
we need a clean way to programmatically check for errors during a parse and handling them after the fact,
sometimes one syntax error causes several others (when not recovered in line, for instance), so it can be helpful to group or nest these errors by parent context when displaying output to the user and you can't know all the errors until the parse is finished,
you may want to display errors differently to the user depending on how many and how severe they are, for example, a single error that exited a rule or a few errors all recovered in line might just ask the user to fix these local areas - otherwise, you might have the user edit the entire input, and you need to have all the errors to make this determination.
The bottom line is that we can be smarter about reporting and asking users to fix syntax errors if we know the full context in which the errors occurred (including other errors). To do this, I have the following three goals:
a full collection of all the errors from a given parse,
context information for each error, and
severity and recovery information for each error.
I have written code to do #1 and #2, and I'm looking for help on #3. I'm also going to suggest some small changes to make #1 and #2 easier for everyone.
First, to accomplish #1 (a full collection of errors), I created CollectionErrorListener as follows:
public class CollectionErrorListener extends BaseErrorListener {
private final List<SyntaxError> errors = new ArrayList<SyntaxError>();
public List<SyntaxError> getErrors() {
return errors;
}
#Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
if (e == null) {
// e is null when the parser was able to recover in line without exiting the surrounding rule.
e = new InlineRecognitionException(msg, recognizer, ((Parser)recognizer).getInputStream(), ((Parser)recognizer).getContext(), (Token) offendingSymbol);
}
this.errors.add(new SyntaxError(msg, e));
}
}
And here is my class for InlineRecognitionException:
public class InlineRecognitionException extends RecognitionException {
public InlineRecognitionException(String message, Recognizer<?, ?> recognizer, IntStream input, ParserRuleContext ctx, Token offendingToken) {
super(message, recognizer, input, ctx);
this.setOffendingToken(offendingToken);
}
}
And here is my class for the SyntaxError container:
public class SyntaxError extends RecognitionException {
public SyntaxError(String message, RecognitionException e) {
super(message, e.getRecognizer(), e.getInputStream(), (ParserRuleContext) e.getCtx());
this.setOffendingToken(e.getOffendingToken());
this.initCause(e);
}
}
This is very similar to the SyntaxErrorListener referred to by 280Z28's answer to Antlr error/exception handling. I need both the InlineRecognitionException and the SyntaxError wrapper because of how the parameters of CollectionErrorListener.syntaxError are filled.
First of all, the RecognitionException parameter "e" is null if the parser recovered from the exception in line (without leaving the rule). We can't just instantiate a new RecognitionException because there is no constructor or method that allows us to set the offending token. Anyway, being able to differentiate errors that were recovered in line (using an instanceof test) is useful information for achieving goal #3, so we can use the class of InlineRecognitionException to indicate in line recovery.
Next, we need the SyntaxError wrapper class because, even when RecognitionException "e" is not null (e.g., when recovery was not in line), the value of e.getMessage() is null (for some unknown reason). We therefore need to store the msg parameter to CollectionErrorListener.syntaxError. Because there is no setMessage() modifier method on RecognitionException, and we can't just instantiate a new RecognitionException (we lose the offending token information as discussed in the previous paragraph), we are left subclassing to be able to set the message, offending token, and cause appropriately.
And this mechanism works really well:
CollectionErrorListener collector = new CollectionErrorListener();
parser.addErrorListener(collector);
ParseTree tree = parser.prog();
// ... Later ...
for (SyntaxError e : collector.getErrors()) {
// RecognitionExceptionUtil is my custom class discussed next.
System.out.println(RecognitionExceptionUtil.formatVerbose(e));
}
This gets to my next point. Formatting output from a RecognitionException is kinda annoying. Chapter 9 of The Definitive ANTLR 4 Reference book shows how displaying quality error messages means you need to split the input lines, reverse the rule invocation stack, and piece together a lot of stuff from the offending token to explain where the error occurred. And, the following command doesn't work if you are reporting errors after the parse is finished:
// The following doesn't work if you are not reporting during the parse because the
// parser context is lost from the RecognitionException "e" recognizer.
List<String> stack = ((Parser)e.getRecognizer()).getRuleInvocationStack();
The problem is that we have lost the RuleContext, and that is needed for getRuleInvocationStack. Luckily, RecognitionException keeps a copy of our context and getRuleInvocationStack takes a parameter, so here is how we get the rule invocation stack after the parse is finished:
// Pass in the context from RecognitionException "e" to get the rule invocation stack
// after the parse is finished.
List<String> stack = ((Parser)e.getRecognizer()).getRuleInvocationStack(e.getCtx());
In general, it would be especially nice if we had some convenience methods in RecognitionException to make error reporting more friendly. Here is my first attempt at a utility class of methods that could be part of RecognitionException:
public class RecognitionExceptionUtil {
public static String formatVerbose(RecognitionException e) {
return String.format("ERROR on line %s:%s => %s%nrule stack: %s%noffending token %s => %s%n%s",
getLineNumberString(e),
getCharPositionInLineString(e),
e.getMessage(),
getRuleStackString(e),
getOffendingTokenString(e),
getOffendingTokenVerboseString(e),
getErrorLineStringUnderlined(e).replaceAll("(?m)^|$", "|"));
}
public static String getRuleStackString(RecognitionException e) {
if (e == null || e.getRecognizer() == null
|| e.getCtx() == null
|| e.getRecognizer().getRuleNames() == null) {
return "";
}
List<String> stack = ((Parser)e.getRecognizer()).getRuleInvocationStack(e.getCtx());
Collections.reverse(stack);
return stack.toString();
}
public static String getLineNumberString(RecognitionException e) {
if (e == null || e.getOffendingToken() == null) {
return "";
}
return String.format("%d", e.getOffendingToken().getLine());
}
public static String getCharPositionInLineString(RecognitionException e) {
if (e == null || e.getOffendingToken() == null) {
return "";
}
return String.format("%d", e.getOffendingToken().getCharPositionInLine());
}
public static String getOffendingTokenString(RecognitionException e) {
if (e == null || e.getOffendingToken() == null) {
return "";
}
return e.getOffendingToken().toString();
}
public static String getOffendingTokenVerboseString(RecognitionException e) {
if (e == null || e.getOffendingToken() == null) {
return "";
}
return String.format("at tokenStream[%d], inputString[%d..%d] = '%s', tokenType<%d> = %s, on line %d, character %d",
e.getOffendingToken().getTokenIndex(),
e.getOffendingToken().getStartIndex(),
e.getOffendingToken().getStopIndex(),
e.getOffendingToken().getText(),
e.getOffendingToken().getType(),
e.getRecognizer().getTokenNames()[e.getOffendingToken().getType()],
e.getOffendingToken().getLine(),
e.getOffendingToken().getCharPositionInLine());
}
public static String getErrorLineString(RecognitionException e) {
if (e == null || e.getRecognizer() == null
|| e.getRecognizer().getInputStream() == null
|| e.getOffendingToken() == null) {
return "";
}
CommonTokenStream tokens =
(CommonTokenStream)e.getRecognizer().getInputStream();
String input = tokens.getTokenSource().getInputStream().toString();
String[] lines = input.split(String.format("\r?\n"));
return lines[e.getOffendingToken().getLine() - 1];
}
public static String getErrorLineStringUnderlined(RecognitionException e) {
String errorLine = getErrorLineString(e);
if (errorLine.isEmpty()) {
return errorLine;
}
// replace tabs with single space so that charPositionInLine gives us the
// column to start underlining.
errorLine = errorLine.replaceAll("\t", " ");
StringBuilder underLine = new StringBuilder(String.format("%" + errorLine.length() + "s", ""));
int start = e.getOffendingToken().getStartIndex();
int stop = e.getOffendingToken().getStopIndex();
if ( start>=0 && stop>=0 ) {
for (int i=0; i<=(stop-start); i++) {
underLine.setCharAt(e.getOffendingToken().getCharPositionInLine() + i, '^');
}
}
return String.format("%s%n%s", errorLine, underLine);
}
}
There is a lot to be desired in my RecognitionExceptionUtil (always returning strings, not checking that recognizer is of type Parser, not handling multiple lines in getErrorLineString, etc), but I'm hoping you get the idea.
SUMMARY of my suggestions for a future version of ANTLR:
Always populate the "RecognitionException e" parameter of ANTLRErrorListener.syntaxError (including the OffendingToken) so that we can collect these exceptions for batch handling after the parse. While your at it, make sure the e.getMessage() is set to return the value currently in the msg parameter.
Add a constructor for RecognitionException that includes OffendingToken.
Remove the other parameters in the method signature of ANTLRErrorListener.syntaxError since they will be extraneous and lead to confusion.
Add convenience methods in RecognitionException for common stuff such as getCharPositionInLine, getLineNumber, getRuleStack, and the rest of my stuff from my RecognitionExceptionUtil class defined above. Of course, these will have to check for null and also check that recognizer is of type Parser for some of these methods.
When calling ANTLRErrorListener.syntaxError, clone the recognizer so that we don't lose the context when the parse finishes (and we can more easily call getRuleInvocationStack).
If you clone the recognizer, you won't need to store the context in RecognitionException. We can make two changes to e.getCtx(): first, rename it to e.getContext() to make it consistent with Parser.getContext(), and second, make it a convenience method for the recognizer we already have in RecognitionException (checking that recognizer is an instance of Parser).
Include information in RecognitionException about the severity of the error and how the parser recovered. This is my goal #3 from the beginning. It would be great to categorize syntax errors by how well the parser handled it. Did this error blow up the entire parse or just show up as a blip in line? How many and which tokens were skipped / inserted?
So, I'm looking for feedback on my three goals and especially any suggestions for gathering more information about goal #3: severity and recovery information for each error.
I posted these suggestions to the Antlr4 GitHub Issue list and received the below reply. I believe that the ANTLRErrorListener.syntaxError method contains redundant / confusing parameters and requires a lot of API knowledge to use properly, but I understand the decision. Here is the link to the issue and a copy of the text response:
From: https://github.com/antlr/antlr4/issues/396
Regarding your suggestions:
Populating the RecognitionException e argument to syntaxError: As mentioned in the documentation:
The RecognitionException is non-null for all syntax errors except when
we discover mismatched token errors that we can recover from in-line,
without returning from the surrounding rule (via the single token
insertion and deletion mechanism).
Adding a constructor to RecognitionException with the offending token: This is not really relevant to this issue, and would be addressed separately (if at all).
Removing parameters from syntaxError: This would not only introduce breaking changes for users who have implemented this method in previous releases of ANTLR 4, but it would eliminate the ability to report the available information for errors which occurred inline (i.e. errors where no RecognitionException is available).
Convenience methods in RecognitionException: This is not really relevant to this issue, and would be addressed separately (if at all). (Further note: It's hard enough as-is to document the API. This just adds more ways to do things that are already readily accessible, so I oppose this change.)
Cloning the recognizer when calling syntaxError: This is a performance-critical method, so new objects are only created when absolutely necessary.
"If cloning the recognizer": The recognizer will never be cloned before calling syntaxError.
This information can be stored in an associative map in your implementation of ANTLRErrorListener and/or ANTLRErrorStrategy if necessary for your application.
I'm closing this issue for now since I don't see any action items requiring changes to the runtime from this list.

JAVA: JUnitTest, testing method that throws two exceptions

I'm testing a method that throws two different exceptions. This is my header:
#Test (expected = A8InvalidInputException.class)
public void testGuessCharacter() throws A8InvalidInputException, A8AlreadyGuessedException
{
...
}
The body has two try/catch blocks (a search on SO resulted in a post that said that's how you test that exceptions are thrown), one for each exception. It seems to me I should break this up into two test methods, especially because I can only have one expected attribute. However, when I do that, the method that is supposed to be testing A8InvalidInputException is requiring a try/catch for A8AlreadyGuessedException, and the method that is supposed to be testing A8AlreadyGuessedException is requiring a try/catch for A8InvalidInputException. I'm not really sure how to write this test. This is the method I'm trying to test:
/**
* This method returns whether a specified character exists in the keyPhrase field
* #param guess a character being checked for in the keyPhrase field
* #return returns whether a specified character exists in the keyPhrase field
* #throws A8InvalidInputException if a non-valid character is passed as input to this method
* #throws A8AlreadyGuessedException if a valid character which has already been guessed is passed as input to this method
*/
public boolean guessCharacter(char guess) throws A8InvalidInputException, A8AlreadyGuessedException
{
if(isValidCharacter(guess))
{
guess = Character.toLowerCase(guess);
if(guessedCharacters.contains(guess) )
{
throw new A8AlreadyGuessedException("" + guess);
}
else
{
guessedCharacters.add(guess);
if(keyPhrase.contains("" + guess))
return true;
else
{
numberOfGuessesLeft--;
return false;
}
}
}
else
{
throw new A8InvalidInputException("" + guess);
}
}
Just add both exceptions in the throws clause:
#Test (expected = A8InvalidCharacterException.class)
public void testInvalidCharacterScenario() throws A8InvalidInputException, A8AlreadyGuessedException { ... }
#Test (expected = A8InvalidInputException.class)
public void testInvalidInputScenario() throws A8InvalidInputException, A8AlreadyGuessedException { ... }
Then, if one test throws the other exception (the unexpected one) then your test will automatically fail.
A method can throw only one exception when it is ran. That’s why there can be only one expected attributed.
You may want to have three test cases: one when the method throws one exception, one when the method throws the other, and one when the method doesn’t throw any exception at all.
Do not put any try/catch statement in your #Test methods, just declare that they throw exceptions.
Yes, you should break this up into two unit tests. One with an invalid input to trigger the A8InvalidInputException and another with an "already guessed" input to trigger the A8AlreadyGuessedException.
Consider, to make writing your tests a little simpler, breaking them out into two distinct portions - testing isValidCharacter and testing guessCharacter separately.
Presuming that isValidCharacter(guess) will fail if you receive an invalid guess, I think that throwing A8InvalidInputException in that method would be ideal.
public boolean isValidCharacter(char guess) throws A8InvalidInputException {
// have your logic to check the guess, and if it's invalid, throw
}
Then all you would need to do is test that particular method to see if it throws the exception on bogus input.
#Test (expected = A8InvalidInputException.class)
public void testIsValidCharacterWithInvalidCharacter() {
// write your test here.
}
Next, you can change your method to only care about the happy-path of the isValidCharacter method, since if you don't return the boolean, you've thrown the exception.
Lastly, you would only concern the tests for guessCharacter around whether or not it throws A8AlreadyGuessedException.
#Test (expected = A8AlreadyGuessedException.class)
public void testGuessCharacterWithAlreadyGuessedValue() {
// write your test here.
}

Categories