Fluent assertion framework for Java that provides clear, readable test assertions and informative failure messages
—
Specialized assertions for throwables and exceptions including message and cause chain validation.
Entry point for creating assertions about exceptions and throwables.
/**
* Creates a ThrowableSubject for asserting about Throwable instances.
* @param actual the Throwable under test
*/
public static ThrowableSubject assertThat(Throwable actual);Usage Examples:
try {
someMethodThatThrows();
fail("Expected exception to be thrown");
} catch (Exception e) {
assertThat(e).isInstanceOf(IllegalArgumentException.class);
}Methods for asserting about exception messages using StringSubject capabilities.
/**
* Returns a StringSubject for making assertions about the exception's message.
* The message is obtained via getMessage().
*/
public StringSubject hasMessageThat();Usage Examples:
IllegalArgumentException exception =
new IllegalArgumentException("Invalid input: null value not allowed");
// Basic message assertions
assertThat(exception).hasMessageThat().contains("Invalid input");
assertThat(exception).hasMessageThat().startsWith("Invalid");
assertThat(exception).hasMessageThat().endsWith("not allowed");
// Pattern matching in messages
assertThat(exception).hasMessageThat().matches("Invalid input: .* not allowed");
assertThat(exception).hasMessageThat().containsMatch("null value");
// Case-insensitive message checks
assertThat(exception).hasMessageThat().ignoringCase().contains("INVALID");Methods for asserting about exception causes, enabling deep cause chain validation.
/**
* Returns a ThrowableSubject for making assertions about the exception's cause.
* The cause is obtained via getCause().
*/
public ThrowableSubject hasCauseThat();Usage Examples:
// Exception with nested causes
IOException ioException = new IOException("File not found");
RuntimeException runtimeException = new RuntimeException("Processing failed", ioException);
// Asserting about direct cause
assertThat(runtimeException).hasCauseThat().isInstanceOf(IOException.class);
assertThat(runtimeException).hasCauseThat().hasMessageThat().contains("File not found");
// Chaining cause assertions for deep cause chains
DatabaseException dbException = new DatabaseException("Connection failed");
ServiceException serviceException = new ServiceException("Service unavailable", dbException);
ApplicationException appException = new ApplicationException("Request failed", serviceException);
assertThat(appException)
.hasCauseThat().isInstanceOf(ServiceException.class)
.hasCauseThat().isInstanceOf(DatabaseException.class)
.hasMessageThat().contains("Connection failed");Real-world patterns for testing exception behavior in applications.
// Method that should throw exception
@Test
public void testInvalidInput_ThrowsException() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> calculator.divide(10, 0)
);
assertThat(exception).hasMessageThat().contains("Division by zero");
}
// Using Truth with assertThrows
@Test
public void testFileOperation_ThrowsIOException() {
IOException exception = assertThrows(
IOException.class,
() -> fileService.readNonExistentFile("missing.txt")
);
assertThat(exception).hasMessageThat().startsWith("File not found");
assertThat(exception).hasCauseThat().isNull();
}// Testing validation exceptions with detailed messages
@Test
public void testUserValidation_InvalidEmail() {
ValidationException exception = assertThrows(
ValidationException.class,
() -> userService.createUser("invalid-email", "password")
);
assertThat(exception).hasMessageThat().contains("email");
assertThat(exception).hasMessageThat().matches(".*email.*format.*");
assertThat(exception).hasMessageThat().ignoringCase().contains("INVALID");
}
// Testing multiple validation errors
@Test
public void testUserValidation_MultipleErrors() {
ValidationException exception = assertThrows(
ValidationException.class,
() -> userService.createUser("", "")
);
assertThat(exception).hasMessageThat().contains("email");
assertThat(exception).hasMessageThat().contains("password");
}// Testing proper exception wrapping in service layers
@Test
public void testServiceLayer_WrapsDataAccessExceptions() {
// Simulate data access exception
when(userRepository.findById(1L)).thenThrow(
new DataAccessException("Database connection timeout")
);
ServiceException exception = assertThrows(
ServiceException.class,
() -> userService.getUser(1L)
);
// Verify exception wrapping
assertThat(exception).hasMessageThat().contains("Failed to retrieve user");
assertThat(exception).hasCauseThat().isInstanceOf(DataAccessException.class);
assertThat(exception).hasCauseThat().hasMessageThat().contains("Database connection timeout");
}// Testing authentication exceptions
@Test
public void testAuthentication_InvalidCredentials() {
AuthenticationException exception = assertThrows(
AuthenticationException.class,
() -> authService.authenticate("user", "wrongpassword")
);
assertThat(exception).hasMessageThat().contains("Invalid credentials");
assertThat(exception).hasMessageThat().doesNotContain("password"); // Security: don't leak password details
}
// Testing authorization exceptions
@Test
public void testAuthorization_InsufficientPermissions() {
AuthorizationException exception = assertThrows(
AuthorizationException.class,
() -> adminService.deleteUser(regularUser, targetUserId)
);
assertThat(exception).hasMessageThat().contains("Insufficient permissions");
assertThat(exception).hasMessageThat().containsMatch("user.*not authorized");
}// Testing configuration validation
@Test
public void testConfiguration_MissingProperty() {
ConfigurationException exception = assertThrows(
ConfigurationException.class,
() -> configService.loadConfig("invalid-config.properties")
);
assertThat(exception).hasMessageThat().contains("Missing required property");
assertThat(exception).hasMessageThat().matches(".*database\\.url.*required.*");
}
// Testing configuration format errors
@Test
public void testConfiguration_InvalidFormat() {
ConfigurationException exception = assertThrows(
ConfigurationException.class,
() -> configService.loadConfig("malformed-config.xml")
);
assertThat(exception).hasMessageThat().contains("Invalid XML format");
assertThat(exception).hasCauseThat().isInstanceOf(XMLParseException.class);
}// Testing timeout exceptions
@Test
public void testNetworkCall_Timeout() {
TimeoutException exception = assertThrows(
TimeoutException.class,
() -> networkService.callExternalApi()
);
assertThat(exception).hasMessageThat().contains("timeout");
assertThat(exception).hasMessageThat().containsMatch("\\d+ms"); // Contains timeout duration
}
// Testing resource exhaustion
@Test
public void testResourceExhaustion_OutOfMemory() {
ResourceException exception = assertThrows(
ResourceException.class,
() -> memoryIntensiveOperation()
);
assertThat(exception).hasMessageThat().contains("Out of memory");
assertThat(exception).hasCauseThat().isInstanceOf(OutOfMemoryError.class);
}// Testing custom business exceptions
@Test
public void testBusinessLogic_InvalidBusinessRule() {
BusinessRuleException exception = assertThrows(
BusinessRuleException.class,
() -> orderService.processOrder(invalidOrder)
);
assertThat(exception).hasMessageThat().contains("Business rule violation");
assertThat(exception).hasMessageThat().contains(invalidOrder.getId().toString());
assertThat(exception.getErrorCode()).isEqualTo("INVALID_ORDER_STATE");
}
// Testing exception with custom properties
public class CustomException extends Exception {
private final String errorCode;
private final int statusCode;
// constructor and getters...
}
@Test
public void testCustomException_Properties() {
CustomException exception = assertThrows(
CustomException.class,
() -> customService.performOperation()
);
assertThat(exception).hasMessageThat().contains("Operation failed");
assertThat(exception.getErrorCode()).isEqualTo("CUSTOM_ERROR");
assertThat(exception.getStatusCode()).isEqualTo(500);
}// Testing that exception originates from expected location
@Test
public void testException_OriginatesFromCorrectLocation() {
RuntimeException exception = assertThrows(
RuntimeException.class,
() -> serviceMethodThatShouldFail()
);
assertThat(exception).hasMessageThat().isNotNull();
// Verify stack trace contains expected method
StackTraceElement[] stackTrace = exception.getStackTrace();
assertThat(stackTrace).isNotEmpty();
boolean foundExpectedMethod = Arrays.stream(stackTrace)
.anyMatch(element -> element.getMethodName().equals("serviceMethodThatShouldFail"));
assertThat(foundExpectedMethod).isTrue();
}/**
* Comprehensive exception testing helper method
*/
public static void assertExceptionDetails(
Throwable exception,
Class<? extends Throwable> expectedType,
String expectedMessageSubstring,
Class<? extends Throwable> expectedCauseType) {
assertThat(exception).isInstanceOf(expectedType);
assertThat(exception).hasMessageThat().contains(expectedMessageSubstring);
if (expectedCauseType != null) {
assertThat(exception).hasCauseThat().isInstanceOf(expectedCauseType);
}
}
// Usage
@Test
public void testComplexOperation_ProperExceptionHandling() {
ServiceException exception = assertThrows(
ServiceException.class,
() -> complexService.performComplexOperation()
);
assertExceptionDetails(
exception,
ServiceException.class,
"Complex operation failed",
DataAccessException.class
);
}/**
* Subject class for making assertions about Throwable instances.
*/
public class ThrowableSubject extends Subject {
/**
* Constructor for ThrowableSubject.
* @param metadata failure metadata for context
* @param actual the Throwable under test
*/
protected ThrowableSubject(FailureMetadata metadata, Throwable actual);
/**
* Returns a StringSubject for making assertions about the exception's message.
* The message is obtained via getMessage().
*/
public StringSubject hasMessageThat();
/**
* Returns a ThrowableSubject for making assertions about the exception's cause.
* The cause is obtained via getCause().
*/
public ThrowableSubject hasCauseThat();
}Install with Tessl CLI
npx tessl i tessl/maven-com-google-truth--truth