Unit testing is an integral part of the software development process, such that individual units of code behave as expected.
During unit tests, mocking is often used to manage external dependencies. One such behavior that can be tested is throwing exceptions. To experience effective mock exceptions using Mockito (a widely used mocking framework for Java) allows one to analyze error conditions and validate the code’s resilience.
Overview
Common Scenarios for Mocking Exceptions
- Testing Error Handling: Validate how the code responds to exceptions.
- Simulating API Failures: Mock external service failures without real dependencies.
- Handling Database Exceptions: Test how applications react to DB connection failures.
- Validating Retry Mechanisms: Ensure retry logic works correctly in case of failures.
- Testing Edge Cases: Simulate unexpected errors to improve robustness.
Methods of Mocking Exceptions in Mockito
- when().thenThrow(): Throws an exception when a method is called.
- doThrow().when(): Used for mocking exceptions in void methods.
- Custom Exception Handling: Mock custom exceptions to test specific behaviors.
- Chained Exceptions: Define multiple exceptions for sequential calls
In this blog, learn more about mocking exceptions, different methods to throw exceptions and real-life examples.
Why Mock Exceptions in Unit Testing?
Mock exceptions in unit testing allow us to analyze various error scenarios that the code may experience in real-world usage. It is important for handling exceptions effectively and for building resilient applications. By mocking exceptions, users can easily test how the code responds to failure conditions without relying on actual exceptions or complex setups.
Mocking exceptions allow users to:
- Duplicate error conditions for better coverage.
- Test the code conditions in exceptional scenarios.
- Avoid relying on external systems that may be slow or difficult to configure for error scenarios.
Common Scenarios for Mocking Exceptions
Here are some of the general scenarios where mocking exceptions are identified:
- External dependencies: When testing a class that interacts with external services, like databases or APIs, users might want to assume failures from these services.
- Exception handling: For testing classes with different exception types, mocking those exceptions allows for handling conditions without any complex setup.
- Timeouts and network errors: Analyze network failures or timeouts in classes that depend on remote calls.
What is Mockito?
Mockito is a robust mocking framework used to create mock objects in unit tests for Java applications. It allows developers to analyze and verify the behavior of real objects, ensuring that tests can focus on specific functionality without requiring complex setups.
Mockito helps to mock methods, verify interactions, and simulate different return values or behaviors, including throwing exceptions.
With Mockito, users can mock objects and set them up to return specific values or throw exceptions under controlled conditions. It provides an intuitive API, making it easy to create mocks, define their behavior, and verify interactions.
Read More: Exception Handling in JavaScript
Throwing Exceptions with Mockito
Mocking exceptions is one of the core features of Mockito. It allows us to understand the system’s reaction to different situations.
1. Using when().thenThrow() for Non-Void Methods
The most common way to mock an exception in Mockito is by using the when().thenThrow() method. This approach is used when a user wants a specific method to throw an exception when it is called.
Example:
import static org.mockito.Mockito.*; import org.junit.jupiter.api.Test; class MyServiceTest { @Test void testThrowException() { MyClass mockClass = mock(MyClass.class); when(mockClass.someMethod()).thenThrow(new RuntimeException("An error occurred")); assertThrows(RuntimeException.class, () -> { mockClass.someMethod(); }); } }
This example demonstrates how to mock a RuntimeException for the someMethod() (which is non-void method) call using when().thenThrow().
2. Throwing Exceptions in Void Methods with doThrow()
For methods that return void, users can’t use when().thenThrow() since there’s no return value to mock. Instead, Mockito provides the doThrow() method to mock exceptions in void methods.
import static org.mockito.Mockito.*; import org.junit.jupiter.api.Test; class MyServiceTest { @Test void testThrowExceptionInVoidMethod() { MyClass mockClass = mock(MyClass.class); doThrow(new IllegalArgumentException("Invalid argument")).when(mockClass).voidMethod(); assertThrows(IllegalArgumentException.class, () -> { mockClass.voidMethod(); }); } }
In this example, the doThrow() method is used to mock an llegalArgumentException when the voidMethod() is called.
Examples of Mocking Exceptions in Mockito
Some of the examples of mocking exceptions in Mockito are:
1. Mocking Checked Exceptions
Mockito can also be used to mock checked exceptions (Exceptions are checked at compile time, forcing the programmer to handle them explicitly), which require explicit handling in code.
When mocking checked exceptions, users should declare the method to throw the exception.
import static org.mockito.Mockito.*; import org.junit.jupiter.api.Test; import java.io.IOException; class MyServiceTest { @Test void testThrowCheckedException() throws IOException { MyClass mockClass = mock(MyClass.class); when(mockClass.readFile()).thenThrow(new IOException("File not found")); assertThrows(IOException.class, () -> { mockClass.readFile(); }); } }
In this example, the readFile() method is mocked to throw an IOException when called, simulating a file read error
2. Handling Runtime Exceptions
Mockito can easily handle runtime exceptions, which do not require an explicit declaration in the method signature. The process of mocking runtime exceptions is similar to mocking checked exceptions.
import static org.mockito.Mockito.*; import org.junit.jupiter.api.Test; class MyServiceTest { @Test void testThrowRuntimeException() { MyClass mockClass = mock(MyClass.class); when(mockClass.processData()).thenThrow(new IllegalStateException("Processing failed")); assertThrows(IllegalStateException.class, () -> { mockClass.processData(); }); } }
In this example, the processData() method is mocked to throw an IllegalStateException, and the test verifies that the exception is thrown correctly.
3. Throwing Multiple Exceptions for Sequential Cells
Mockito also supports throwing different exceptions for subsequent method calls using thenThrow().
import static org.mockito.Mockito.*; import org.junit.jupiter.api.Test; class MyServiceTest { @Test void testThrowMultipleExceptions() { MyClass mockClass = mock(MyClass.class); when(mockClass.someMethod()) .thenThrow(new IllegalArgumentException("Invalid")) .thenThrow(new NullPointerException("Null value")); // First call throws IllegalArgumentException assertThrows(IllegalArgumentException.class, () -> { mockClass.someMethod(); }); // Second call throws NullPointerException assertThrows(NullPointerException.class, () -> { mockClass.someMethod(); }); } }
In this example:
1. The first call to someMethod() throws an IllegalArgumentException with the message “Invalid”.
2. The second call to someMethod() throws a NullPointerException with the message “Null value“.
Read More: Java Debugging Tools and Techniques
3. Each exception is asserted using assertThrows().
4. Mocking Exceptions with Message for Void Methods
For void methods, users can add a message to the exception being thrown. This is useful when a user wants to access specific error messages for debugging or logging purposes.
import static org.mockito.Mockito.*; import org.junit.jupiter.api.Test; class MyServiceTest { @Test void testThrowExceptionWithMessageInVoidMethod() { MyClass mockClass = mock(MyClass.class); doThrow(new RuntimeException("Void method failed")).when(mockClass).voidMethod(); RuntimeException exception = assertThrows(RuntimeException.class, () -> { mockClass.voidMethod(); }); assertEquals("Void method failed", exception.getMessage()); } }
In this example:
1. The voidMethod() is mocked to throw a RuntimeException with the message “Void method failed”.
2. The test checks that the exception is thrown and ensures the message matches the expected value.
5. Mocking Exceptions with Message for Non-Void Methods
Mockito allows you to mock exceptions with specific messages for non-void methods. This helps users to analyse error conditions in methods that return a result, while also verifying the exception message.
import static org.mockito.Mockito.*; import org.junit.jupiter.api.Test; class MyServiceTest { @Test void testThrowExceptionWithMessageForNonVoidMethod() { MyClass mockClass = mock(MyClass.class); when(mockClass.calculate()).thenThrow(new ArithmeticException("Division by zero")); ArithmeticException exception = assertThrows(ArithmeticException.class, () -> { mockClass.calculate(); }); assertEquals("Division by zero", exception.getMessage()); } }
In this example:
1. The calculate() method (non void method) is mocked to throw an ArithmeticException with the message “Division by zero“.
2. The test checks that the exception is thrown correctly and verifies that the message in the exception matches the expected message.
Read More: Top 15 Unit Testing Tools
Best Practices for Mocking Exceptions
Some of the best practices for mocking exceptions are:
- Be Specific: Mock only the exceptions that are relevant to the test. Avoid over-mocking to ensure that tests remain focused and maintainable.
- Handle Executions Properly: Confirm that the application code is prepared to handle exceptions correctly, even when mocked during tests.
- Use Descriptive Error Messages: When mocking exceptions, include meaningful messages to help diagnose issues if tests fail.
- Test Exception Handling Logic: Make sure that the code handles the exceptions as expected.
Why Test Mocked Exceptions on Real Devices?
While Mockito provides an effective solution for unit testing exceptions, it’s important to consider that code should behave as expected in a real user conditions. Testing on real devices can expose issues related to device-specific configurations, network conditions, or other environmental factors that may not be captured in unit tests.
BrowserStack Automate allows Java tests to be run on real browsers, devices, and environments, providing detailed insights and ensuring that code performs reliably across diverse platforms. It eliminates the need to maintain complex local testing environments, allowing users to scale the test execution more efficiently.
Conclusion
Mocking exceptions in unit tests is a strong technique that ensures the handling of code errors is more efficient.
By using Mockito’s simple yet flexible API, users can analyze various exceptions, test how the system reacts, and ensure robust error handling.
BrowserStack Automate comes as an ideal solution for teams looking to streamline their testing workflows and ensure their applications deliver seamless experiences to users, no matter the device or browser.