Author

Author- Ram Ranjeet Kumar
Showing posts with label Junit5. Show all posts
Showing posts with label Junit5. Show all posts

Tuesday, August 15, 2023

Write Junit Test case for a Java class with all functionality



To use JUnit in your Maven project, you need to add the JUnit dependency to your pom.xml file. Additionally, if you're using JUnit 5 (JUnit Jupiter), you'll also need to include the JUnit Jupiter API and the JUnit Jupiter Engine dependencies. Here's how you can do it:

<dependencies>

    <!-- JUnit 5 -->

    <dependency>

        <groupId>org.junit.jupiter</groupId>

        <artifactId>junit-jupiter-api</artifactId>

        <version>5.8.1</version>

        <scope>test</scope>

    </dependency>

    <dependency>

        <groupId>org.junit.jupiter</groupId>

        <artifactId>junit-jupiter-engine</artifactId>

        <version>5.8.1</version>

        <scope>test</scope>

    </dependency>

</dependencies>


 Here's the Calculator class

public class Calculator {


    public int add(int a, int b) {

        return a + b;

    }


    public int subtract(int a, int b) {

        return a - b;

    }


    public int multiply(int a, int b) {

        return a * b;

    }


    public double divide(int a, int b) {

        if (b == 0) {

            throw new ArithmeticException("Cannot divide by zero");

        }

        return (double) a / b;

    }

}


This is the Calculator class that performs basic arithmetic operations. Make sure both the Calculator class and the JUnit test class are in the same package or properly imported for the test cases to work as intended.

Here's how you can write JUnit test cases to test its functionality:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class CalculatorTest {

    @Test
    public void testAddition() {
        Calculator calculator = new Calculator();
        int result = calculator.add(5, 7);
        assertEquals(12, result);
    }

    @Test
    public void testSubtraction() {
        Calculator calculator = new Calculator();
        int result = calculator.subtract(10, 3);
        assertEquals(7, result);
    }

    @Test
    public void testMultiplication() {
        Calculator calculator = new Calculator();
        int result = calculator.multiply(4, 6);
        assertEquals(24, result);
    }

    @Test
    public void testDivision() {
        Calculator calculator = new Calculator();
        double result = calculator.divide(15, 3);
        assertEquals(5.0, result, 0.001); // Using delta for double comparison
    }

    @Test
    public void testDivisionByZero() {
        Calculator calculator = new Calculator();
        assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0));
    }
}


In this example, the Calculator class has methods for addition, subtraction, multiplication, and division. The JUnit test class CalculatorTest contains several test methods:
  • testAddition: Tests the addition method.
  • testSubtraction: Tests the subtraction method.
  • testMultiplication: Tests the multiplication method.
  • testDivision: Tests the division method.
  • testDivisionByZero: Tests whether division by zero throws an ArithmeticException.
Remember to have your Calculator class and JUnit library properly set up in your project for these test cases to work. Additionally, you might need to adjust the code based on your specific project structure and requirements.

Saturday, August 12, 2023

@MethodSource Annotation With @ParameterizedTest in JUnit5



  • The @MethodSource annotation is a part of the JUnit 5 testing framework in Java.
  •  It is used to specify a method as the source of test cases for parameterized tests. 
  • Parameterized tests allow you to run the same test logic with multiple sets of input parameters, making your testing more comprehensive and efficient.
  • The @MethodSource annotation is used to indicate that the source of test cases for a parameterized test method comes from another method. 
  • This annotation is applied to the parameterized test method itself.
  • @MethodSource is an annotation that allows you to provide arguments for a parameterized test from a factory method.
  • The factory method can be in the same class as the test or in an external class. 
  • The factory method must return a stream, iterable, iterator, or array of arguments. 

Example:
Let's say you have a simple class called MathUtils with a method add(int a, int b) that adds two integers

public class MathUtils {
    public static int add(int a, int b) {
        return a + b;
    }
}

Now, you want to write parameterized tests for the add method using JUnit 5 and the @MethodSource annotation.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class MathUtilsTest {

    @ParameterizedTest
    @MethodSource("additionProvider")
    void testAddition(int a, int b, int expectedResult) {
        int result = MathUtils.add(a, b);
        assertEquals(expectedResult, result);
    }

    static Stream<Arguments> additionProvider() {
        return Stream.of(
            Arguments.of(2, 3, 5),
            Arguments.of(-1, 1, 0),
            Arguments.of(0, 0, 0)
        );
    }
}

In this example:
  • The testAddition method is a parameterized test method that takes three parameters: a, b, and expectedResult.
  • The @MethodSource("additionProvider") annotation specifies that the test cases will be provided by the method named additionProvider.
  • The additionProvider method returns a Stream<Arguments> containing sets of arguments for the parameterized test.
  • When you run the tests, JUnit 5 will generate separate test cases for each set of arguments provided by the additionProvider method. 
  • This way, you can easily test the add method with various input values without writing multiple individual test methods.

Let's consider another example using a different data type. Suppose you have a class called StringUtils with a method concatenate(String a, String b) that concatenates two strings

public class StringUtils {
    public static String concatenate(String a, String b) {
        return a + b;
    }
}

Now, you want to write parameterized tests for the concatenate method using JUnit 5 and the @MethodSource annotation.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class StringUtilsTest {

    @ParameterizedTest
    @MethodSource("concatenationProvider")
    void testConcatenation(String a, String b, String expectedResult) {
        String result = StringUtils.concatenate(a, b);
        assertEquals(expectedResult, result);
    }

    static Stream<Arguments> concatenationProvider() {
        return Stream.of(
            Arguments.of("Hello, ", "world!", "Hello, world!"),
            Arguments.of("", "Test", "Test"),
            Arguments.of("Java ", "Programming", "Java Programming")
        );
    }
}


In this example:

  • The testConcatenation method is a parameterized test method that takes three parameters: a, b, and expectedResult.
  • The @MethodSource("concatenationProvider") annotation specifies that the test cases will be provided by the method named concatenationProvider.
  • The concatenationProvider method returns a Stream<Arguments> containing sets of arguments for the parameterized test.
  • When you run the tests, JUnit 5 will generate separate test cases for each set of arguments provided by the concatenationProvider method. This allows you to test the concatenate method with various combinations of strings without writing repetitive test code.

Here is an example of using @MethodSource annotation with an iterable:

@ParameterizedTest
@MethodSource("provideStringsForIsBlank")
void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input, boolean expected) {
    assertEquals(expected, Strings.isBlank(input));
}

static Stream<Arguments> provideStringsForIsBlank() {
    return Stream.of(
      Arguments.of(null, true),
      Arguments.of("", true),
      Arguments.of("  ", true),
      Arguments.of("not blank", false)
    );
}

Here is an example of using @MethodSource annotation with an iterator:

@ParameterizedTest
@MethodSource("provideIntegers")
void testWithIteratorSource(int argument) {
    assertNotEquals(9, argument);
}

static Iterator<Integer> provideIntegers() {
    return Arrays.asList(1, 2, 3, 4, 5).iterator();
}

Here is an example of using @MethodSource annotation with an array:

@ParameterizedTest
@MethodSource("provideStrings")
void testWithArraySource(String argument) {
    assertNotNull(argument);
}

static String[] provideStrings() {
    return new String[] {"hello", "world"};
}

Remember to add the appropriate JUnit 5 dependencies to your project to use these annotations and features.


Sunday, August 6, 2023

@CsvSource Annotation With @ParameterizedTest in JUnit5





  • The @CsvSource annotation is a way to provide comma-separated values (CSV) as arguments for a parameterized test method in JUnit 5. 
  • It allows you to specify one or more CSV records that will be used as the source of arguments for each invocation of the test method. 
  • You can customize the column delimiter, the quote character, the empty value, and the null values using the annotation attributes. 
  • You can also use a text block to write multiple CSV records in a more readable way.
  • You can use various types of arguments and datatypes, such as primitives, enums, arrays, collections, and custom types. 
  • However, you need to make sure that the arguments can be converted to the target parameter types by using implicit or explicit converters.


Here is an example of how to use the @CsvSource annotation:

import org.junit.jupiter.params.ParameterizedTest;

import org.junit.jupiter.params.provider.CsvSource;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @ParameterizedTest

    @CsvSource({

        "0,    1,   1",

        "1,    2,   3",

        "49,  51, 100",

        "1,  100, 101"

    })

    void add(int first, int second, int expectedResult) {

        Calculator calculator = new Calculator();

        assertEquals(expectedResult, calculator.add(first, second),

                () -> first + " + " + second + " should equal " + expectedResult);

    }

}


  • This test method will be executed four times with different values for the first, second, and expectedResult parameters. 
  • The values are taken from the CSV records provided by the @CsvSource annotation. 
  • The column delimiter is a comma (,) by default, but you can change it using the delimiter or delimiterString attributes.


Here are some examples of how to use the @CsvSource annotation with different types of arguments and datatypes:


Primitives: You can use primitive types such as int, long, double, boolean, etc. as arguments for the test method. For example:

@ParameterizedTest

@CsvSource({"1, 2, 3", "4, 5, 9", "7, 8, 15"})

void testAdd(int a, int b, int expected) {

    assertEquals(expected, a + b);

}


Enums: You can use enum constants as arguments for the test method. For example:

enum Color {

    RED, GREEN, BLUE

}

@ParameterizedTest

@CsvSource({"RED, 255, 0, 0", "GREEN, 0, 255, 0", "BLUE, 0, 0, 255"})

void testColor(Color color, int r, int g, int b) {

    assertEquals(r, color.getRed());

    assertEquals(g, color.getGreen());

    assertEquals(b, color.getBlue());

}


Arrays: You can use arrays as arguments for the test method by using curly braces ({}) to enclose the array elements. For example:

@ParameterizedTest

@CsvSource({"1, {2, 3}", "4, {5, 6}", "7, {8}"})

void testArray(int a, int[] b) {

    assertTrue(a > 0);

    assertTrue(b.length > 0);

}


Collections: You can use collections such as List or Set as arguments for the test method by using curly braces ({}) to enclose the collection elements. For example:


@ParameterizedTest

@CsvSource({"1, {2, 3}", "4, {5}", "7"})

void testCollection(int a, List<Integer> b) {

    assertTrue(a > 0);

    assertNotNull(b);

}


Custom types: You can use custom types as arguments for the test method by implementing an ArgumentConverter that can convert a String to the custom type. For example:


class Person {

    private String name;

    private int age;

    // constructor and getters omitted for brevity

}


class PersonConverter implements ArgumentConverter {

    @Override

    public Object convert(Object source, ParameterContext context) throws ArgumentConversionException {

        if (!(source instanceof String)) {

            throw new IllegalArgumentException("The argument should be a string: " + source);

        }

        String[] parts = ((String) source).split(":");

        if (parts.length != 2) {

            throw new IllegalArgumentException("The argument should have two parts separated by a colon: " + source);

        }

        String name = parts[0];

        int age = Integer.parseInt(parts[1]);

        return new Person(name, age);

    }

}


@ParameterizedTest

@CsvSource({"Alice:23", "Bob:42", "Charlie:28"})

void testPerson(@ConvertWith(PersonConverter.class) Person person) {

    assertNotNull(person.getName());

    assertTrue(person.getAge() > 0);

}


You can use a different delimiter character or string for the CSV records, such as a semicolon (;) or a pipe (|). For example:

@ParameterizedTest

@CsvSource (delimiter = ';', value = {

    "apple; 1",

    "banana; 2",

    "'lemon; lime'; 3"

})

void testWithSemicolonDelimiter(String fruit, int rank) {

    assertNotNull(fruit);

    assertNotEquals(0, rank);

}


You can use a different quote character for the CSV records, such as a double quote (\"). For example:

@ParameterizedTest

@CsvSource (quoteCharacter = '"', value = {

    "\"apple, banana\"; 1",

    "\"lemon, lime\"; 2"

})

void testWithDoubleQuote(String fruits, int rank) {

    assertTrue(fruits.contains(","));

    assertNotEquals(0, rank);

}


You can use a text block to write multiple CSV records in a more readable way. For example:

@ParameterizedTest

@CsvSource (textBlock = """

    name, age

    Alice, 23

    Bob, 42

    Charlie, 28

""")

void testWithTextBlock(String name, int age) {

    assertNotNull(name);

    assertTrue(age > 0);

}


You can use the nullValues attribute to specify one or more strings that should be interpreted as null references. For example:

@ParameterizedTest

@CsvSource (nullValues = {"NULL", "N/A"}, value = {

    "apple, NULL",

    "banana, N/A",

    "lemon, ''"

})

void testWithNullValues(String fruit, String color) {

    assertNotNull(fruit);

    assertNull(color);

}

You can find more information and examples about the @CsvSource annotation in the [JUnit 5 documentation]. 

Sunday, July 30, 2023

Junit Assertions




Assertion in Junit

In JUnit, assertions are used to verify the expected behavior of your code during testing. They allow you to check if certain conditions are true or false and report the test results accordingly. If an assertion fails, the test is considered to have failed.

JUnit provides a set of static methods in the Assertions class (JUnit 5) or Assert class (JUnit 4) for making assertions. Here are some common assertion methods:
  • assertEquals(expected, actual): This assertion checks if the expected value is equal to the actual value.
import org.junit.jupiter.api.Assertions;
@Test
public void testAddition() {
    int result = add(3, 5);
    Assertions.assertEquals(8, result);
}

  • assertTrue(condition): This assertion checks if the given condition is true.
import org.junit.jupiter.api.Assertions;
@Test
public void testIsPositive() {
    int number = 10;
    Assertions.assertTrue(number > 0);
}

  • assertFalse(condition): This assertion checks if the given condition is false.
import org.junit.jupiter.api.Assertions;
@Test
public void testIsNegative() {
    int number = -5;
    Assertions.assertFalse(number > 0);
}

  • assertNull(object): This assertion checks if the given object is null.
import org.junit.jupiter.api.Assertions;
@Test
public void testIsNull() {
    Object obj = null;
    Assertions.assertNull(obj);
}

  • assertNotNull(object): This assertion checks if the given object is not null.
import org.junit.jupiter.api.Assertions;
@Test
public void testIsNotNull() {
    Object obj = new Object();
    Assertions.assertNotNull(obj);
}

  • assertSame(expected, actual): This assertion checks if the expected and actual references point to the same object.
import org.junit.jupiter.api.Assertions;
@Test
public void testSameReference() {
    Object obj1 = new Object();
    Object obj2 = obj1;
    Assertions.assertSame(obj1, obj2);
}

  • assertNotSame(expected, actual): This assertion checks if the expected and actual references do not point to the same object.
import org.junit.jupiter.api.Assertions;
@Test
public void testDifferentReferences() {
    Object obj1 = new Object();
    Object obj2 = new Object();
    Assertions.assertNotSame(obj1, obj2);
}

  • assertThrows(exceptionType, executable): This assertion checks if the executable throws an exception of the specified exceptionType.
import org.junit.jupiter.api.Assertions;
@Test
public void testDivideByZero() {
    int a = 10;
    int b = 0;
    Assertions.assertThrows(ArithmeticException.class, () -> divide(a, b));
}


These are some of the commonly used assertion methods in JUnit. Depending on the version of JUnit you are using (JUnit 5 or JUnit 4), the assertion methods may have slight differences in naming and package import. Always make sure to import the appropriate assertion class based on your JUnit version.

Junit5 Annotations


 

JUnit5 annotations

  • @Test: This annotation is similar to the one in JUnit 4 and is used to mark a method as a test method.
@Test
public void testAddition() {
    // Test logic and assertions go here
}


  • @BeforeEach: This annotation is used to mark a method that should be executed before each test method in the class. It is typically used to set up the test environment or initialize resources.
@BeforeEach
public void setUp() {
    // Initialization logic goes here
}
  • @AfterEach: This annotation is used to mark a method that should be executed after each test method in the class. It is typically used to clean up resources or perform cleanup operations.
@AfterEach
public void tearDown() {
    // Cleanup logic goes here
}

  • @BeforeAll: This annotation is used to mark a method that should be executed once before any test methods in the class. It is used for setup that should be performed only once.
@BeforeAll
public static void setUpClass() {
    // One-time setup logic goes here
}

  • @AfterAll: This annotation is used to mark a method that should be executed once after all test methods in the class have been executed. It is used for cleanup that should be performed only once.
@AfterAll
public static void tearDownClass() {
    // One-time cleanup logic goes here
}

  • @RepeatedTest : This annotation is used in JUnit 5 to repeat a test multiple times. The simplest way of using the annotation is to pass an integer value as an argument. Here is an example of how to use the annotation:
@RepeatedTest(5)
public void testRepeatedTest() {
    System.out.println("Hello World");
}
This will run the annotated method 5 times.

@ParameterizedTest:  This annotation is used in JUnit 5 to run the same test multiple times with different arguments. Here is an example of how to use the annotation:

@ParameterizedTest
@ValueSource(strings = { "madam", "java", "racecar" })
void palindromes(String value) {
    assertTrue(StringUtils.isPalindrome(value));
}

This will run the test method three times with the arguments "madam", "java, and "racecar" respectively.

  • @TestFactory: This  annotation is used in JUnit 5 to create dynamic tests. A dynamic test is a test that is generated at runtime by a factory method using the @TestFactory annotation. The method marked @TestFactory is not a test case, rather it’s a factory for test cases. Here is an example of how to use the annotation:
@TestFactory
Stream<DynamicTest> dynamicTestsFromStream() {
    return Stream.of("racecar", "radar", "able was I ere I saw elba")
      .map(text -> DynamicTest.dynamicTest("Palindrome test for " + text, () -> assertTrue(StringUtils.isPalindrome(text))));
}

This will create three dynamic tests with the arguments "racecar", "radar", and "able was I ere I saw elba" respectively.


  • @Disabled: This annotation is used to disable a test method or an entire test class during test execution. It is similar to @Ignore in JUnit 4.
@Disabled
@Test
public void disabledTestMethod() {
    // This test method will be disabled during test execution
}
  • @DisplayName: This annotation is used to provide a custom display name for a test method or a test class. It allows you to give more descriptive names to your tests.
@Test
@DisplayName("Test addition operation")
public void testAddition() {
    // Test logic and assertions go here
}
  • @Nested: This annotation is used to define nested test classes. Nested test classes can access the private fields and methods of the outer class, allowing better test organization.
public class OuterTestClass {

    @Nested
    class InnerTestClass {
        // Test methods and other annotations go here
    }
}

  • @TestClassOrder:  It is used in JUnit 5 to configure a ClassOrderer for the @Nested test classes of the annotated test class. If @TestClassOrder is not explicitly declared on a test class, inherited from a parent class, declared on a test interface implemented by a test class, or inherited from an enclosing class, @Nested test classes will be executed in arbitrary order. As an alternative to @TestClassOrder, a global ClassOrderer can be configured for the entire test suite via the "junit.jupiter.testclass.order.default" configuration parameter¹. 

Here's an example of how to use @TestClassOrder:


@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class OrderedNestedTests {
    @Nested
    @Order(1)
    class PrimaryTests {
        // @Test methods ...
    }

    @Nested
    @Order(2)
    class SecondaryTests {
        // @Test methods ...
    }
}

This will guarantee that @Nested test classes are executed in the order specified via the @Order annotation.


  • @TestMethodOrder:  It is used in JUnit 5 to control the execution order of tests. We can use our own MethodOrderer, as we'll see later. Or we can select one of three built-in orderers:
    • Alphanumeric Order, 
    • @Order Annotation, 
    • Random Order. 

Here's an example of how to use @TestMethodOrder

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderedTests {
    @Test
    @Order(1)
    void nullValues() {}

    @Test
    @Order(2)
    void emptyValues() {}

    @Test
    @Order(3)
    void validValues() {}
}

This will guarantee that test methods are executed in the order specified via the @Order annotation




These are some of the essential annotations provided by JUnit 5. JUnit 5 also introduces many other features and improvements, such as parameterized tests, dynamic tests, test interfaces, and more, making it a powerful and modern testing framework for Java developers.