Lecture 4_5

Download as pdf or txt
Download as pdf or txt
You are on page 1of 44

61FIT3SE1 - Software Engineering 1

Lecture 4
Unit test with java
Outline
• Unit testing and test case
• JUnit
• Mockito
References

• Boni García, Mastering Software Testing with JUnit 5


What is unit test?

Testing levels contains 4 phases:


• Unit testing
• Integration testing
• System testing
• Acceptance testing

Acceptance User testing (validation)

System

Integration Development testing


(verification)
Unit

Testing levels
Why unit test?

• Quality Issue Identification


• Validation of Functional Requirements
• Simplified Debugging
Unit Test case

• A test case is a sample of executions


• In a unit test case, there are 4 phases executed in sequence:
• Setup: Initializes the test fixture, that is the before picture required for the unit
to exhibit the expected behavior.
• Exercise: Run the given unit with input data to get some outcome (actual
value) as a result
• Verify: Determine whether the expected value has been obtained
• Teardown: Tear down the test fixture to put the methods back into the initial
state
• Unit testing is done with the unit under test in isolation
JUnit

• JUnit is a testing framework which allows to create automated tests


• JUnit was developed by Kent Beck and Erich Gamma in late 1995
• Nowadays, it is considered as the practice standard for testing Java
applications
• Two versions:
• JUnit 4: On February 18, 2006, JUnit 4.0 was released
• Junit 5: Junit 5.0 was released on September 10, 2017
Junit 5 architecture

• JUnit 5 framework is composed of three major components:


• Platform
• Jupiter
• Vintage
Test lifecycle in Junit 5

• Unit test case is composed of 4 stages. To control these test phases, Junit
5 using different annotations.

Annotation Description JUnit 4 equivalence


@Test Identify the methods that have to be @Test
executed as test cases.
@BeforeEach Method executed before each @Test in
@Before
the current class
@AfterEach Method executed after each @Test in the
@After
current class
@BeforeAll Method executed before all @Test in the
@BeforeClass
current class
@AfterAll Method executed after all @Test in the
@AfterClass
current class
Test lifecycle in Junit 5

• The order of execution of annotations in a Java class:

Jupiter annotations to control test lifecycle


Test lifecycle in Junit 5

• Mapping the annotations to control the test lifecycle with the different
parts of a test case:

Stages JUnit 5 JUnit 4


Setup (optional) @BeforeAll @BeforeClass
@BeforeEach @Before
Exercise
@Test @Test
Verify
Teardown (optional) @AfterEach @After
@AfterAll @AfterClass
Example of a unit test case

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class LifecycleJUnit5Test { @Test
@BeforeAll void testTwo() {
static void setupAll() { System.out.println("TEST 2");
System.out.println("Setup ALL }
TESTS in the class"); @AfterEach
} void teardown() {
@BeforeEach System.out.println("Teardown
void setup() { EACH TEST in the class");
System.out.println("Setup EACH }
TEST in the class"); @AfterAll
} static void teardownAll() {
@Test System.out.println("Teardown
void testOne() { ALL TESTS in the class");
System.out.println("TEST 1"); }
} }
Annotations

• @Disabled: Skip test case


• @DisplayName: Display custom name for the test class or test method
• @Tag: Tag a test method or test class for filtering tests
• @AutoClose: automatically close resources associated with fields.
• @RepeatedTest: repeat a test a specified number of times
Assertions

• In verify stage, comparing the outcome (actual value) from exercise


stage with the expected result is called assertions.
• An assertion (also known as a predicate) is a boolean statement typically
used to reason about software correctness.
• An assertion is composed of three parts:
• Expected value: comes from test documents.
• Actual value (real outcome): comes from the exercise stage
• Logic comparator: use to compare these 2 values
• As a result of an assertion, we obtain a test verdict, which, in the end, is
going to define if the test has succeeded or failed.
Assertions in Jupiter JUnit 5

• All JUnit Jupiter assertions are static methods in the Assertions class
located in org.junit.jupiter package.
Assertion Description
fail Fails a test with a given message and/or exception
assertTrue Asserts that a supplied condition is true
assertFalse Asserts that a supplied condition is false
assertNull Asserts that a supplied object is null
assertNotNull Asserts that a supplied object is not null
assertEquals Asserts that two supplied objects are equal
assertArrayEquals Asserts that two supplied arrays are equal
assertIterableEquals Asserts that two iterable objects are deeply equal
assertLinesMatch Asserts that two lists of Strings are equals
assertNotEquals Asserts that two supplied objects are not equal
assertSame Asserts that two objects are the same, compared with ==
assertNotSame Asserts that two objects are different, compared with !=
Special assertions

• Jupiter provides some special assertions:

Assertion Description
assertAll Group of assertions: this method allows to group different
assertions at the same time. In a grouped assertion, all
assertions are always executed, and any failures will be
reported together
assertThrows Asserting exceptions: this assertion allows to verify if a given
exception is raised in a piece of code.
assertTimeout Asserting timeouts: this assertion allows us to verify the
timeout of a given operation.
assertTimeoutPreemtively Asserting timeouts: this assertion allows us to verify the
timeout of a given operation. The difference with
assertTimeoutPreemptively with respect to
assertTimeout is that
assertTimeoutPreemptively does not wait until the
end of the operation, and the execution is aborted when the
expected timeout is exceeded
Example
class StandardAssertionsTest {
@Test
void standardAssertions() {
assertEquals(2, 2);
assertTrue(true, "The optional assertion message is now the
last parameter");
assertFalse(false, () -> "Really " + "expensive " +
"message" + ".");
}
}
class GroupedAssertionsTest {
@Test
void groupedAssertions() {
Address address = new Address("John", "Smith");
assertAll("address", () -> assertEquals("John",
address.getFirstName()), () -> assertEquals("User",
address.getLastName()));
}
}
Example
class ExceptionTest {
@Test
void exceptionTesting() {
Throwable exception =
assertThrows(IllegalArgumentException.class, () -> { throw new
IllegalArgumentException("a message");});
assertEquals("a message", exception.getMessage());
}
}
class TimeoutExceededTest {
@Test
void timeoutNotExceeded() {
assertTimeout(ofMinutes(2), () -> {
Thread.sleep(100); });
}
@Test
void timeoutExceeded() {
assertTimeout(ofMillis(10), () -> {
Thread.sleep(100); });
}
}
Example
class TimeoutWithResultOrMethodTest {
@Test
void timeoutNotExceededWithResult() {
String actualResult = assertTimeout(ofMinutes(1), () -> {
return "hi there"; });
assertEquals("hi there", actualResult);
}
@Test
void timeoutNotExceededWithMethod() {
String actualGreeting = assertTimeout(ofMinutes(1),
TimeoutWithResultOrMethodTest::greeting);
assertEquals("hello world!", actualGreeting);
}
private static String greeting() { return "hello world!"; }
}
class TimeoutWithPreemptiveTerminationTest {
@Test void timeoutExceededWithPreemptiveTermination() {
assertTimeoutPreemptively(ofMillis(10), () -> {
Thread.sleep(100); });
}
}
Advanced JUnit Features

• Dynamic tests: Generate tests at runtime


• Parameterized tests: A special kinds of tests in which the data input is
injected in the test in order to reuse the same test logic
Dynamic Tests

• JUnit 5 allows to generate test at runtime by a factory method that is


annotated with @TestFactory
• A @TestFactory method must return a Stream, Collection,
Iterable, or Iterator of DynamicTest instances
• In order to create a dynamic test, we can use the static method
dynamicTest of the class DynamicTest located in the
org.junit.jupiter.api package. Stream (in
java.util.stream.Stream) is used to store a given set of input
data.
• There is another possibility to create dynamic tests in JUnit 5, using the
static method stream of the class DynamicTest. This stream
method needs an input generator, a function that generates a display
name based on an input value, and a test executor.
Example
class CollectionTest {
// Warning: this test will raise an exception
@TestFactory
List<String> dynamicTestsWithInvalidReturnType() {
return Arrays.asList("Hello"); }
@TestFactory
Collection<DynamicTest> dynamicTestsFromCollection() {
return Arrays.asList( dynamicTest("1st dynamic test", () ->
assertTrue(true)), dynamicTest("2nd dynamic test", () ->
assertEquals(4, 2 * 2))); }
@TestFactory
Iterable<DynamicTest> dynamicTestsFromIterable() {
return Arrays.asList( dynamicTest("3rd dynamic test", () ->
assertTrue(true)), dynamicTest("4th dynamic test", () ->
assertEquals(4, 2 * 2))); }
@TestFactory
Iterator<DynamicTest> dynamicTestsFromIterator() {
return Arrays.asList( dynamicTest("5th dynamic test", () ->
assertTrue(true)), dynamicTest("6th dynamic test", () ->
assertEquals(4, 2 * 2))).iterator(); }
}
Example
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

class DynamicExampleTest {
@TestFactory
Stream<DynamicTest> dynamicTestsFromStream() {
Stream<String> inputStream = Stream.of("A", "B", "C");
return inputStream.map( input -> dynamicTest("Display name
for input " + input, () -> { System.out.println("Testing " +
input); })); }
}
Example
class StreamExampleTest {
@TestFactory
Stream<DynamicTest> streamTest() {
// Input data
Integer array[] = { 1, 2, 3 };
Iterator<Integer> inputGenerator =
Arrays.asList(array).iterator();

// Display names
Function<Integer, String> displayNameGenerator = ( input) -
> "Data input:" + input;

// Test executor
ThrowingConsumer<Integer> testExecutor = (input) -> {
System.out.println(input); assertTrue(input % 2 == 0); };

// Returns a stream of dynamic tests


return stream(inputGenerator, displayNameGenerator,
testExecutor); }
}
Parameterized Tests

• Parameterized tests are a special kinds of tests in which the data input is
injected in the test in order to reuse the same test logic.
• Steps to create parameterized tests:
• We need to use the annotation @ParameterizedTest (located in the
package org.junit.jupiter.params) to declare a method within a
Java class as a parameterized test.
• We need to specify at least one argument provider.
Parameterized Tests
Arguments provider Description
annotation
@ValueSource Used to specify an array of literal values of String, int, long, or double
@EnumSource Argument source for constants of a specified enumeration
(java.lang.Enum)
@MethodSource Provides access to values returned by static methods of the class in
which this annotation is declared
@CsvSource Argument source which reads comma-separated values (CSV) from its
attribute
@CsvFileSource Argument source which is used to load CSV files from one or more
classpath resources
@ArgumentSource Used to specify a custom argument provider (that is, a Java class that
implements the interface)
org.junit.jupiter.params.provider.ArgumentsProvider)
@ValueSource

• The annotation @ValueSource is used in conjunction with


@ParameterizedTest to specify a parameterized test in which the
argument source is an array of literal values of String, int, long, or
double.

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


import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class ValueSourceStringsParameterizedTest {
@ParameterizedTest
@ValueSource(strings = { "Hello", "World" })
void testWithStrings(String argument) {
System.out.println("Parameterized test with (String)
parameter: " + argument);
assertNotNull(argument);
}
}
@EnumSource

• The annotation @EnumSource allows to specify a parameterized test in


which the argument source is a Java enumeration class.

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


import java.util.concurrent.TimeUnit;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

class EnumSourceParameterizedTest {
@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithEnum(TimeUnit argument) {
System.out.println("Parameterized test with (TimeUnit)
argument: " + argument);
assertNotNull(argument); }
}
@MethodSource

• The annotation @MethodSource allows to define the name of the


static method in which the arguments for the test are provided as a Java
8 Stream.
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

class MethodSourceStringsParameterizedTest {
static Stream stringProvider() {
return Stream.of("hello", "world"); }

@ParameterizedTest
@MethodSource("stringProvider")
void testWithStringProvider(String argument) {
System.out.println("Parameterized test with (String)
argument: " + argument);
assertNotNull(argument);
}
}
@CsvSource and @CsvFileSource

• Another way to specify the source of arguments for parameterized tests


is using comma-separated values (CSV). This can be done using the
annotation @CsvSource
• If the amount of CSV data is big, it might be more convenient using the
annotation @CsvFileSource instead. This annotation allows to feed
the parameterized test with a CSV file located in the classpath of the
project
• The content of the CSV is automatically converted based on a set of rules
boolean/Boolean "false" -> false
byte/Byte "1" -> (byte) 1
char/Character "a" -> 'a'
short/Short "2" -> (short) 2
int/Integer "3" -> 3
long/Long "4" -> 4L
float/Float "5.0" -> 5.0f
double/Double "6.0" -> 6.0d
Example
class CsvSourceParameterizedTest {
@ParameterizedTest
@CsvSource({ "hello, 1", "world, 2", "'happy, testing', 3" })
void testWithCsvSource(String first, int second) {
System.out.println("Parameterized test with (String) " +
first + " and (int) " + second);
assertNotNull(first);
assertNotEquals(0, second); }
}
class CsvFileSourceParameterizedTest {
@ParameterizedTest
@CsvFileSource(resources = "/input.csv")
void testWithCsvFileSource(String first, int second) {
System.out.println("Yet another parameterized test with
(String) " + first + " and (int) " + second);
assertNotNull(first);
assertNotEquals(0, second); }
}
input.csv:
How are you, 4
“hi, there”, 5
@ArgumentsSource

• @ArgumentsSource aimed to specify the source of arguments for


parameterized tests. We can specify a custom (and reusable in different
tests) class, which will contain the parameters for the test. This class
must implement the interface
org.junit.jupiter.params.provider.ArgumentsProvid
er, must override method provideArguments. This function
returns a Stream of Arguments.

class ArgumentSourceParameterizedTest {
@ParameterizedTest
@ArgumentsSource(CustomArgumentsProvider1.class)
void testWithArgumentsSource(String first, int second) {
System.out.println("Parameterized test with (String) " +
first + " and (int) " + second);
assertNotNull(first);
assertTrue(second > 0); }
}
@ArgumentsSource

import java.util.stream.Stream;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;

public class CustomArgumentsProvider1 implements


ArgumentsProvider {

@Override
public Stream<? extends Arguments> provideArguments(
ExtensionContext context) {
System.out.println("Arguments provider to test " +
context.getTestMethod().get().getName());
return Stream.of(Arguments.of("hello", 1),
Arguments.of("world", 2));
}
}
Mockito

• Mockito (http://site.mockito.org/) is a trending open source mock unit


testing framework for Java, first released in April 2008.
• Why do we need mock unit in unit test?
Mockito

• Mockito is a testing framework that allows mock object creation,


stubbing, and verification.
• Mockito provides an API to isolate the SUT (System Under Test ) and its
DOCs (Depended-On Components) . Using Mockito involves three
different steps:
• Mocking objects
• Setting expectations
• Verification
Mocking object

• A mock object is a dummy implementation for an interface or a class. It


allows to define the output of certain method calls. They typically record
the interaction with the system and tests can validate that.
• To create mock object, we can have 2 ways:
• @Mock annotation on fields
• Using the static mock() method

@Mock
MyDoc docMock;

MyDoc docMock = Mockito.mock(MyDoc.class)

Mockito API Description


@Mock This annotation identifies a mock object to be created by Mockito.
This is used typically for DOC(s).
@InjectMocks This annotation identifies the object in which the mocks are going to
be injected. This is used typically to the unit we want to test, that is,
our SUT.
Setting expectations

• The differential aspect of mocks object with respect to other test doubles
(such as stub) is that mock objects can be programmed with custom
expectations according to the needs of the unit test.
• This process in the Mockito is known as stubbing methods, in which
these methods belong to the mocks.
Mockito API Description
Mockito.when(x).thenReturn(y) These methods allow us to specify the value (y)
Mockito.doReturn(y).when(x) that should be returned by the stubbed method
(x) of a given mock object.
Mockito.when(x).thenThrow(e) These methods allow us to specify the exception
Mockito.doThrow(e).when(x) (e) that should be thrown when calling a stubbed
method (x) of a given mock object.
Mockito.when(x).thenAnswer(a) Unlike returning a hardcoded value, a dynamic
Mockito.doAnswer(a).when(x) user defined logic (Answer a) is executed when a
given method (x) of the mock is invoked.
Mockito.when(x).thenCallRealMethod() This method allows us the real implementation of
Mockito.doCallRealMethod().when(x) a method instead the mocked one.
Verification

• Mockito provides a powerful API to carry out different types of


verifications.
Mockito API Description

Mockito.verify() This method verifies the invocation of mock objects. This


verification can be optionally enhanced using the following
methods:
• times(n): The stubbed method is invoked exactly n times.
• never(): The stubbed method is never called.
• atLeastOnce(): The stubbed method is invoked at least once.
• atLeast(n): The stubbed method is called at least n times.
• atMost(n): The stubbed method is called at the most n times.
• only(): A mock fails if any other method is called on the mock
object.
• timeout(m): This method is called in m milliseconds at the
most.
Mockito.verifyZeroIntera These two methods verify that a stubbed method has no
ctions() interactions. Internally, they use the same implementation.
Mockito.verifyNoMoreIn
teractions()
Example Mockito and JUnit 5

We suppose that a user interacts with a system made up by three classes:


• LoginController: The class which receives the request from the user, returning
a response as a result. This request is dispatched to the LoginService
component.
• LoginService: This class implements the functionality of the use case. To that
aim, it needs to confirm whether or not the user is authenticated in the
system. To that, it needs to read the persistence layer, implemented in the
LoginRepository class.
• LoginRepository: This class allows to access the persistence layer of the
system, typically implemented by means of a database. This class can also be
called Data Access Object (DAO).
LoginController
public class LoginController {
public LoginService loginService = new LoginService();

public String login(UserForm userForm) {


System.out.println("LoginController.login " + userForm);
try {
if (userForm == null) { return "ERROR"; }
else if (loginService.login(userForm)) { return "OK"; }
else { return "KO"; }
} catch (Exception e) { return "ERROR"; }
}

public void logout(UserForm userForm) {


System.out.println("LoginController.logout " + userForm);
loginService.logout(userForm);
}
}
UserForm
public class UserForm {
public String username;
public String password;

public UserForm(String username, String password) {


this.username = username;
this.password = password;
}

// Getters and setters


@Override
public String toString() {
return "UserForm [username=" + username + ", password=" +
password + "]";
}
}
Test LoginController using JUnit 5 and Mockito
@ExtendWith(MockitoExtension.class)
class LoginControllerLoginTest {
// Mocking objects
@InjectMocks
LoginController loginController;

@Mock
LoginService loginService;
// Test data
UserForm userForm = new UserForm("foo", "bar");
@Test
void testLoginOk() {
// Setting expectations (stubbing methods)
when(loginService.login(userForm)).thenReturn(true);
// Exercise SUT
String reseponseLogin = loginController.login(userForm);
// Verification
assertEquals("OK", reseponseLogin);
verify(loginService).login(userForm);
verifyNoMoreInteractions(loginService);
}
}
Notes

• Data in test case includes: input data and expected result

• Define before the class when use Mockito


@ExtendWith(MockitoExtension.class)
Summary

• Test case
• JUnit
• Mockito

You might also like