Guide to JUnit 5 Functional Interfaces

  • July 12, 2024
Table Of Contents

In this article, we will get familiar with JUnit 5 functional interfaces. JUnit 5 significantly advanced from its predecessors. Features like functional interfaces can greatly simplify our work once we grasp their functionality.

Example Code

This article is accompanied by a working code example on GitHub.

Quick Introduction to Java Functional Interfaces

Functional interfaces are a fundamental concept in Java functional programming. Java 8 specifically designed them to allow the usage of lambda expressions or method references while processing data streams. In the Java API, specifically in the java.util.function package, you will find a collection of functional interfaces. The main characteristic of a functional interface is that it contains only one abstract method. Although it can have default methods and static methods, these do not count towards the single abstract method requirement. Functional interfaces serve as the targets for lambda expressions or method references.

JUnit 5 Functional Interfaces

JUnit functional interfaces belong to the org.junit.jupiter.api.function package. It defines three functional interfaces: Executable, ThrowingConsumer<T> and ThrowingSupplier<T>. We typically use them with the Assertions utility class.

Knowing how these interfaces work can greatly improve your testing methods. They make it easier to write and comprehend tests, reducing the amount of repetitive code needed for handling exceptions. By using these interfaces, you can describe complex test scenarios more clearly and concisely.

Let’s learn how to use these functional interfaces.

Using Executable

Executable is a functional interface that enables the implementation of any generic block of code that may potentially throw a Throwable.

JUnit 5 defines the Executable interface as follows:

@FunctionalInterface
public interface Executable {
    void execute() throws Throwable;
}

The Executable interface defines a single method called execute, which does not have any parameters and does not return anything. It can throw different types of exceptions, making it flexible for handling exceptional situations.

It is particularly useful for writing tests to validate if specific code paths throw specific exceptions.

One common scenario is to use the Executable with the Assertions.assertThrows() method. This method takes an Executable as an argument, executes it, and checks if it throws the expected exception.

The Assertions class provides many assertion methods accepting implementations of the Executable interface:

static void assertAll(Executable... executables)
// other variants of assertAll accepting executables

static void assertDoesNotThrow(Executable executable)
// other variants of assertDoesNotThrow accepting executables

static <T extends Throwable> T assertThrows(Class<T> expectedType,
                                            Executable executable)
// other variants of assertThrows accepting executables

static <T extends Throwable> T assertThrowsExactly(Class<T> expectedType,
                                                   Executable executable)
// other variants of assertThrowsExactly accepting executables

static void assertTimeout(Duration timeout, Executable executable)
// other variants of assertTimeout accepting executables

static void assertTimeoutPreemptively(Duration timeout, Executable executable)
// other variants of assertTimeoutPreemptively accepting executables

Let’s learn about these methods one by one.

Using Executables in assertAll()

The Assertions.assertsAll() method asserts that all supplied executables do not throw exceptions.

Let’s first define the executables which we’ll use in our examples:

public class ExecutableTest {
  private List<Long> numbers = Arrays.asList(100L, 200L, 50L, 300L);
  private Executable sorter =
      () -> {
        TimeUnit.SECONDS.sleep(2);
        numbers.sort(Long::compareTo);
      };
  private Executable checkSorting =
      () -> assertEquals(List.of(50L, 100L, 200L, 300L), numbers);
  private Executable noChanges = 
      () -> assertEquals(List.of(100L, 200L, 50L, 300L), numbers);
  // tests
}

In the ExecutableTest class, we define several tests to demonstrate the usage of the Executable functional interface with JUnit’s timeout assertions.

We start by initializing a list of Long numbers (numbers) and defining two Executable lambdas: sorter and checkSorting. The sorter lambda simulates a time-consuming operation by sleeping for 2 seconds and then sorting the list. The checkSorting lambda verifies that the list is in the correct sort order. Additionally, we define another Executable lambda, noChanges, which checks that the list remains in its initial unsorted state.

Consider the following example that shows how to use assertAll with an executable:

@ParameterizedTest
@CsvSource({"1,1,2,Hello,H,bye,2,byebye",
            "4,5,9,Good,Go,Go,-10,",
            "10,21,31,Team,Tea,Stop,-2,"})
void testAssertAllWithExecutable(int num1, int num2, int sum,
                                  String input, String prefix,
                                  String arg, int count, String result) {
  assertAll(
      () -> assertEquals(sum, num1 + num2),
      () -> assertTrue(input.startsWith(prefix)),
      () -> {
        if (count < 0) {
          assertThrows(
              IllegalArgumentException.class,
              () -> {
                new ArrayList<>(count);
              });
        } else {
          assertEquals(result, arg.repeat(count));
        }
      });
}

In the testAssertAllExecutable() method, Assertions.assertAll() groups 3 assertions provided as a lambda function. The first assertion checks if the sum of num1 and num2 equals sum. The second assertion verifies if the string input starts with prefix. Finally, the third assertion checks if count is less than 0, asserts that creating ArrayList with count will throw IllegalArgumentException, otherwise asserts that string arg repeated count times equals result.

Using Executables in assertDoesNotThrow()

The Assertions.assertDoesNotThrow() method asserts that the execution of the supplied executable does not throw any kind of exception. Thus, we can explicitly verify that the logic under test executes without encountering any exception. It is a useful assertion method that we can use to test the happy paths.

Here’s a simple example:

@ParameterizedTest
@CsvSource({"one,0,o", "one,1,n"})
void testAssertDoesNotThrowWithExecutable(String input, int index, char result) {
    assertDoesNotThrow(() -> assertEquals(input.charAt(index), result));
}

The test testAssertDoesNotThrowWithExecutable() annotated @ParameterizedTest and @CsvSource, runs the test with different sets of parameters. The @CsvSource annotation specifies two sets of parameters: (“one”, 0, ‘o’) and (“one”, 1, ‘n’). For each set of parameters, the test method checks that execution does not throw an exception when verifying that the character at the specified index in the input string matches the expected result.

Using Executables in assertThrows()

The Assertions.assertsThrows() method asserts that the execution of the supplied executable throws an expected exception and returns the exception.

If the logic does not throw any exception or throws a different exception, then this method will fail.

We can perform additional checks on the exception instance we get in the returned value.

It is an useful assertion method we can use to test the failure paths.

Let’s see how we can use assertThrows() in action:

@Test
void testAssertThrowsWithExecutable() {
    List<String> input = Arrays.asList("one", "", "three", null, "five");
    IllegalArgumentException exception =
        assertThrows(
            IllegalArgumentException.class,
            () -> {
                for (String value : input) {
                if (value == null || value.isBlank()) {
                    throw new IllegalArgumentException("Got invalid value");
                }
                // process values
                }
            });
    assertEquals("Got invalid value", exception.getMessage());
}

The testAssertThrowsWithExecutable() method tests the assertThrows() method with an Executable.

It begins by creating a list of strings containing the values “one”, “”, “three”, null, and “five”. Using the assertThrows() method, it checks that executing the lambda expression throws an IllegalArgumentException.

The lambda iterates through the list, and for each string, it checks if the value is null or blank. If it finds a null or blank value, it throws a IllegalArgumentException with the message “Got invalid value".

The assertion confirms that the exception is thrown and verifies that the exception message matches the expected “Got invalid value”.

Using Executables in assertTimeout()

The Assertions.assertTimeout() method asserts that execution of the supplied executable completes before the given timeout. The execution can continue even after it exceeds the timeout. The assertion will throw an exception in case it exceeds the timeout duration.

This is useful to verify if the execution completes within the expected duration.

Here is an example showcasing the use of assertTimeout():

@Test
void testAssertTimeoutWithExecutable() {
  // execution does not complete within expected duration
  assertAll(
    () ->
    assertThrows(
      AssertionFailedError.class, () -> assertTimeout(Duration.ofSeconds(1), sorter)),
    checkSorting);

  // execution completes within expected duration
  assertAll(() -> 
    assertDoesNotThrow(
        () -> assertTimeout(Duration.ofSeconds(5), sorter)), checkSorting);
}

The testAssertTimeoutWithExecutable() method demonstrates the usage of assertTimeout() with an Executable.

In the first assertAll() block, it checks if sorting the list throws a AssertionFailedError within a 1-second timeout. The assertTimeout() method tries to sleep for 2 seconds before sorting. Since it exceeds the timeout, a AssertionFailedError occurs. The checkSorting executable confirms the numbers are in ascending order.

In the second assertAll() block, it verifies that sorting the list within a 5-second timeout does not throw an exception. The assertTimeout() method still sleeps for 2 seconds before sorting. This time, it falls within the 5-second limit, so there is no exception. The checkSorting executable again confirms the numbers are in ascending order.

This shows that the execution continues as expected, but if it takes too long, there is an exception.

Additionally, it illustrates combining multiple assertion techniques for verification.

Using Executables in assertTimeoutPreemptively()

The Assertions.assertTimeoutPreemptively() method asserts that the execution of the supplied executable completes before the given timeout. Furthermore, it aborts the execution of the executable preemptively if it exceeds the timeout.

Let’s see how the preemptive timeout works for the same scenario:

@Test
void testAssertTimeoutPreemptivelyWithExecutable() {
  // execution does not complete within expected duration
  assertAll(
      () ->
          assertThrows(
              AssertionFailedError.class,
              () -> assertTimeoutPreemptively(Duration.ofSeconds(1), sorter)),
      noChanges);

  // execution completes within expected duration
  assertAll(
      () -> assertDoesNotThrow(() -> assertTimeoutPreemptively(Duration.ofSeconds(5), 
                               sorter)),
      checkSorting);
}

The testAssertTimeoutPreemptivelyWithExecutable() method demonstrates how to use the assertTimeoutPreemptively() method with two executables to verify the sorting of a list of numbers.

In the first assertAll() block, the method asserts that the execution throws an AssertionFailedError when sorting the list with a preemptive timeout of 1 second. The assertTimeoutPreemptively() method attempts to sleep for the 2-second delay before sorting the numbers. Since the delay exceeds the 1-second timeout, the assertion fails, and it throws the AssertionFailedError. The noChanges executable then verifies that the list remains unchanged, confirming that assertTimeoutPreemptively() preemptively stopped the sorting operation.

In the second assertAll block, the method asserts that we do not get any exception when sorting the list with a preemptive timeout of 5 seconds. The assertTimeoutPreemptively() method again attempts to sleep for the 2-second delay before sorting the numbers. This time, the delay is within the 5-second timeout, it does not throw any exception. Finally, checkSorting executable confirms that the execution has sorted the list correctly.

Using ThrowingConsumer

The ThrowingConsumer interface serves as a functional interface that enables the implementation of a generic block of code capable of consuming an argument and potentially throwing a Throwable. Unlike the Consumer interface, ThrowingConsumer allows for the throwing of any type of exception, including checked exceptions.

The ThrowingConsumer interface can be handy in scenarios where we need to test code that might throw checked exceptions. This interface allows us to write more concise and readable tests by handling checked exceptions seamlessly.

Here are some typical use cases.

Testing Methods That Throw Checked Exceptions

When we have methods that throw checked exceptions, using ThrowingConsumer can simplify our test code. Instead of wrapping our test logic in try-catch blocks, we can use ThrowingConsumer to write clean and straightforward assertions.

First, let’s define a validation exception our logic would use:

public class ValidationException extends Throwable {
  public ValidationException(String message) {
    super(message);
  }
}

Now, write a test illustrating the use of ThrowingConsumer:

public class ThrowingConsumerTest {

  @ParameterizedTest
  @CsvSource({"50,true", "130,false", "-30,false"})
  void testMethodThatThrowsCheckedException(int percent, boolean valid) {
    // acceptable percentage range: 0 - 100
    ValueRange validPercentageRange = ValueRange.of(0, 100);
    Function<Integer, String> message =
        input ->
            MessageFormat.format(
                "Percentage {0} should be in range {1}", 
                input, validPercentageRange.toString());

    ThrowingConsumer<Integer> consumer =
        input -> {
          if (!validPercentageRange.isValidValue(input)) {
            throw new ValidationException(message.apply(input));
          }
        };

    if (valid) {
      assertDoesNotThrow(() -> consumer.accept(percent));
    } else {
      assertAll(
          () -> {
            ValidationException exception =
                assertThrows(ValidationException.class, 
                             () -> consumer.accept(percent));
            assertEquals(exception.getMessage(), message.apply(percent));
          });
    }
  }
}

In this test, we validate percentage values in the range of 0 to 100 using a ThrowingConsumer. The test method testMethodThatThrowsCheckedException() takes an integer percent and a boolean valid as input. We define a ValueRange object to specify the valid range for percentages.

If the input percentage is not within the valid range, it throws a ValidationException with an appropriate error message. The test covers scenarios for a valid percentage (50), an invalid percentage above the range (130), and an invalid percentage below the range (-30).

The assertions verify that the ThrowingConsumer handles both valid and invalid inputs according to the defined percentage range.

Dynamic Tests with ThrowingConsumer

JUnit 5 offers a powerful feature called dynamic tests, allowing us to create tests at runtime rather than at compile time. This can be especially useful when we don’t know the number of tests or the test data set beforehand.

A common scenario where dynamic tests are beneficial is when you need to validate a series of inputs and their expected outcomes. ThrowingConsumer allows us to define test logic that can throw checked exceptions.

Let’s define a dynamic test for the percentage validation and verify the results:

// Helper record to represent a test case
record TestCase(int percent, boolean valid) {}

@TestFactory
Stream<DynamicTest> testDynamicTestsWithThrowingConsumer() {
  // acceptable percentage range: 0 - 100
  ValueRange validPercentageRange = ValueRange.of(0, 100);
  Function<Integer, String> message =
      input ->
          MessageFormat.format(
              "Percentage {0} should be in range {1}", 
              input, validPercentageRange.toString());

  // Define the ThrowingConsumer that validates the input percentage
  ThrowingConsumer<TestCase> consumer =
      testCase -> {
        if (!validPercentageRange.isValidValue(testCase.percent)) {
          throw new ValidationException(message.apply(testCase.percent));
        }
      };

  ThrowingConsumer<TestCase> executable =
      testCase -> {
        if (testCase.valid) {
          assertDoesNotThrow(() -> consumer.accept(testCase));
        } else {
          assertAll(
              () -> {
                ValidationException exception =
                    assertThrows(ValidationException.class, 
                                 () -> consumer.accept(testCase));
                assertEquals(exception.getMessage(), message.apply(testCase.percent));
              });
        }
      };
  // Test data: an array of test cases with inputs and their validity
  Collection<TestCase> testCases =
      Arrays.asList(new TestCase(50, true), 
                    new TestCase(130, false), 
                    new TestCase(-30, false));

  Function<TestCase, String> displayNameGenerator =
      testCase -> "Testing percentage: " + testCase.percent;

  // Generate dynamic tests
  return DynamicTest.stream(testCases.stream(), displayNameGenerator, executable);
}

First, let’s understand the dynamic test.

class DynamicTest
    public static <T> Stream<DynamicTest> stream(
            Iterator<T> inputGenerator,
            Function<? super T,String> displayNameGenerator,
            ThrowingConsumer<? super T> testExecutor)
    // other variants of stream
}

A DynamicTest is a test case generated at runtime. It is composed of a display name and an Executable. We annotate our test with @TestFactory so that the factory generates instances of DynamicTest.

The stream() method generates a stream of dynamic tests based on the given generator and test executor. Use this method when the set of dynamic tests is nondeterministic or when the input comes from an existing Iterator.

The inputGenerator generates input values and adds a DynamicTest to the resulting stream for each dynamically generated input value. It uses ThrowingConsumer to validate percentage inputs falling within the range of 0 to 100. It defines dynamic tests using a collection of percentages and a boolean indicating validity, with display names generated based on the percentage values. This setup allows for dynamically generating and running tests to ensure the correct handling of both, valid and invalid percentage values.

ThrowingConsumer simplifies testing methods that throw checked exceptions, manage resources, validate inputs, handle callbacks, and process complex data by eliminating extensive try-catch blocks in test code.

Using ThrowingSupplier

ThrowingSupplier is a functional interface that enables the implementation of a generic block of code that returns an object and may throw a Throwable. It is similar to Supplier, except that it can throw any kind of exception, including checked exceptions.

The Assertions class has many assertion methods accepting throwing supplier:

static <T> T assertDoesNotThrow(ThrowingSupplier<T> supplier)
// other variants of assertDoesNotThrow accepting supplier

static <T> T assertTimeout(Duration timeout, ThrowingSupplier<T> supplier)
// other variants of assertTimeout accepting supplier

static void assertTimeoutPreemptively(Duration timeout, ThrowingSupplier<T> supplier)
// other variants of assertTimeoutPreemptively accepting supplier

Let’s learn about these methods one by one.

Using ThrowingSupplier in assertDoesNotThrow()

The method assertDoesNotThrow() asserts that the execution of the supplied supplier does not throw any kind of exception.

If the assertion passes, it returns the supplier’s result. It is useful for testing happy paths.

Let’s look at the implementation of a test using ThrowingSupplier:

public class ThrowingSupplierTest {
  @ParameterizedTest
  @CsvSource({"25.0d,5.0d", "36.0d,6.0d", "49.0d,7.0d"})
  void testDoesNotThrowWithSupplier(double input, double expected) {
    ThrowingSupplier<Double> findSquareRoot =
        () -> {
          if (input < 0) {
            throw new ValidationException("Invalid input");
          }
          return Math.sqrt(input);
        };
    assertEquals(expected, assertDoesNotThrow(findSquareRoot));
  }
}

In this parameterized test, we use ThrowingSupplier to check if the code throws exceptions and verify the return value. The test method, testDoesNotThrowWithSupplier(), performs a square root calculation. The negative input results in a ValidationException. Otherwise, it returns the square root. The test verifies that the ThrowingSupplier executes without exceptions and that the return value matches the expected result.

Using ThrowingSupplier in assertTimeout()

The method assertTimeout() checks that execution of the supplied executable completes before the given timeout. If the running time of the executable exceeds the timeout, the method will throw an AssertionFailedError.

Let’s now check how to use assertTimeout() with a supplier:

@Test
void testAssertTimeoutWithSupplier() {
  List<Long> numbers = Arrays.asList(100L, 200L, 50L, 300L);
  int delay = 2;

  Consumer<List<Long>> checkSorting 
        = list -> assertEquals(List.of(50L, 100L, 200L, 300L), list);

  ThrowingSupplier<List<Long>> sorter =
      () -> {
        if (numbers == null || numbers.isEmpty() || numbers.contains(null)) {
          throw new ValidationException("Invalid input");
        }
        TimeUnit.SECONDS.sleep(delay);
        return numbers.stream().sorted().toList();
      };

  // slow execution
  assertThrows(AssertionFailedError.class, 
               () -> assertTimeout(Duration.ofSeconds(1), sorter));

  // fast execution
  assertDoesNotThrow(
      () -> {
        List<Long> result = assertTimeout(Duration.ofSeconds(5), sorter);
        checkSorting.accept(result);
      });

  // reset the number list and verify if the supplier validates it
  Collections.fill(numbers, null);

  ValidationException exception =
      assertThrows(ValidationException.class, 
                   () -> assertTimeout(Duration.ofSeconds(1), sorter));
  assertEquals("Invalid input", exception.getMessage());
}

In this test, we use ThrowingSupplier and assertTimeout() to verify a sorting operation on a list of numbers. The test method testAssertTimeoutWithSupplier() works with a list of Long numbers and introduces a delay to simulate slow execution.

We test slow and fast execution scenarios and validate the input using assertThrows() and assertDoesNotThrow(). Finally, we demonstrate how to use ThrowingSupplier for operations that may throw checked exceptions and how to verify execution time constraints and input validation using JUnit’s assert methods.

Using ThrowingSupplier in assertTimeoutPreemptively()

The method assertTimeoutPreemptively() asserts that the execution of the supplied supplier completes before the given timeout. It returns the supplier’s result if the assertion passes. If it exceeds the timeout, it will abort the supplier preemptively.

Let’s see an example of assertTimeoutPreemptively() with supplier:

public class ThrowingSupplierTest {
  private List<Long> numbers = Arrays.asList(100L, 200L, 50L, 300L);
  private Consumer<List<Long>> checkSorting =
      list -> assertEquals(List.of(50L, 100L, 200L, 300L), list);

  private ThrowingSupplier<List<Long>> sorter =
      () -> {
        if (numbers == null || numbers.isEmpty() || numbers.contains(null)) {
          throw new ValidationException("Invalid input");
        }
        TimeUnit.SECONDS.sleep(2);
        return numbers.stream().sorted().toList();
      };
}

In this ThrowingSupplierTest class, we define several tests to demonstrate the usage of ThrowingSupplier and JUnit’s timeout assertions.

We start by initializing a list of Long numbers (numbers) and a Consumer<List<Long>> (checkSorting) that checks if the list is in sorted order. We also define a ThrowingSupplier<List<Long>> named sorter, which sorts the list after a delay of 2 seconds. If the list is null, empty, or contains null values, the sorter throws a ValidationException.

Let’s consider a simple example of using a supplier when the test does not throw any exception:

@ParameterizedTest
@CsvSource({"25.0d,5.0d", "36.0d,6.0d", "49.0d,7.0d"})
void testDoesNotThrowWithSupplier(double input, double expected) {
  ThrowingSupplier<Double> findSquareRoot =
      () -> {
        if (input < 0) {
          throw new ValidationException("Invalid input");
        }
        return Math.sqrt(input);
      };
  assertEquals(expected, assertDoesNotThrow(findSquareRoot));
}

In the testDoesNotThrowWithSupplier() method, we use @ParameterizedTest with CsvSource to test the calculation of square roots for different inputs. We define a ThrowingSupplier<Double> named findSquareRoot, which throws a ValidationException for negative inputs. The test uses assertDoesNotThrow to verify that the square root of the input matches the expected value.

Here is an example showcasing the use of timeout with a supplier:

@Test
void testAssertTimeoutWithSupplier() {
  // slow execution
  assertThrows(AssertionFailedError.class, 
               () -> assertTimeout(Duration.ofSeconds(1), sorter));

  // fast execution
  assertDoesNotThrow(
      () -> {
        List<Long> result = assertTimeout(Duration.ofSeconds(5), sorter);
        checkSorting.accept(result);
      });

  // reset the number list and verify if the supplier validates it
  Collections.fill(numbers, null);

  ValidationException exception =
      assertThrows(ValidationException.class, 
                   () -> assertTimeout(Duration.ofSeconds(1), sorter));
  assertEquals("Invalid input", exception.getMessage());
}

In the testAssertTimeoutWithSupplier() method, we test the sorting operation with different timeout durations. First, we verify that the sorting operation fails to complete within 1 second, using assertThrows() to expect a AssertionFailedError.

Then, we test the same operation with a 5-second timeout, using assertDoesNotThrow() to ensure it completes successfully, and the result is in sorted order by the checkSorting consumer.

Next, we reset the numbers list to contain null values and verify that the sorter throws a ValidationException when executed. We use assertThrows() to check that the exception message matches the expected “Invalid input”.

Let’s learn to preemptively time out the execution of a supplier:

@Test
void testAssertTimeoutPreemptivelyWithSupplier() {
  // slow execution
  assertThrows(
      AssertionFailedError.class, 
      () -> assertTimeoutPreemptively(Duration.ofSeconds(1), sorter));

  // fast execution
  assertDoesNotThrow(
      () -> {
        List<Long> result = assertTimeoutPreemptively(Duration.ofSeconds(5), sorter);
        checkSorting.accept(result);
      });
}

Finally, in the testAssertTimeoutPreemptivelyWithSupplier() method, we repeat the timeout tests with assertTimeoutPreemptively(). We verify that the sorting operation fails to complete within 1 second, expecting a AssertionFailedError. We then test the same operation with a 5-second timeout to ensure it completes successfully, and the list is in sorted order.

Conclusion

In this article we got familiar with JUnit 5 functional interfaces, focusing on Executable, ThrowingConsumer, and ThrowingSupplier. These interfaces enhance the flexibility and readability of our test code by allowing us to leverage lambda expressions and method references.

We started with Executable, which encapsulates code that may throw any Throwable. We explored its usage in various JUnit assertions like assertAll, assertTimeout, and assertTimeoutPreemptively, demonstrating how we can use it to group multiple assertions and test time-sensitive operations efficiently.

Next, we examined ThrowingConsumer<T>, which represents an operation that accepts a single input argument and can throw checked exceptions. This interface is particularly useful for scenarios where we need to validate inputs or perform operations that may result in exceptions. We also explored its integration with dynamic tests, showcasing how it can streamline the creation of complex, parameterized test cases.

Finally, we looked at ThrowingSupplier<T>, which provides a value and can throw checked exceptions. This interface simplifies the testing of methods that generate values and might throw exceptions. We demonstrated its use in various timeout assertions, illustrating how it can validate the timely execution of operations and the correctness of generated results.

By understanding and using these functional interfaces, we can write more concise, expressive, and maintainable test code in JUnit 5, ultimately improving the robustness and reliability of our test suites.

Written By:

Sachin Raverkar

Written By:

Sachin Raverkar

Sachin is a Java enthusiast with over two decades of product development expertise. He enjoys architecting and delivering SAAS products as well as sharing expertise with people all over the world.

Recent Posts

Getting Started with Spring Security and JWT

Spring Security provides a comprehensive set of security features for Java applications, covering authentication, authorization, session management, and protection against common security threats such as CSRF (Cross-Site Request Forgery).

Read more

Creating and Publishing an NPM Package with Automated Versioning and Deployment

In this step-by-step guide, we’ll create, publish, and manage an NPM package using TypeScript for better code readability and scalability. We’ll write test cases with Jest and automate our NPM package versioning and publishing process using Changesets and GitHub Actions.

Read more

Offloading File Transfers with Amazon S3 Presigned URLs in Spring Boot

When building web applications that involve file uploads or downloads, a common approach is to have the files pass through an application server.

Read more