Java Expert Exam Notes

Mockito

  • Mockito is a popular open-source framework for mocking objects in software testing.

  • Mocking creates lightweight, configurable imitations of real objects.

  • Mocks simulate complex, real objects (e.g., database connections, network services) when using the real ones is impractical.

Mocking with Mockito

  • Mockito is used to mock objects for testing.

  • Example:

    • A MyService class depends on an OtherService interface.

    • Instead of using a real implementation of OtherService, a mock object is created.

  • The @Mock annotation is used to create mock objects.

  • The @InjectMocks annotation is used to inject mock objects into the class being tested.

  • when(otherService.getValue()).thenReturn(5); defines the behavior of the mock object.

  • assertEquals(500, myService.doCalculation(100)); asserts that the doCalculation method returns the expected value.

Creating Fake (or Dummy) Objects

  • Mocking data objects is useful when:

    • The object is difficult to instantiate.

    • The object has behavior that's being tested elsewhere.

    • The object's state is irrelevant to the test's outcome.

  • Stubbing is configuring the mock and defining what to do when specific methods are called.

  • Two ways of stubbing:

    • when(thisMethodIsCalled).thenReturn(something);

    • doReturn(something).when(thisMock).thisMethodIsCalled(argument);

  • For mocking void methods, use doNothing().when(thisMock).thisMethodIsCalled(argument);

    • or doThrow(Exception.class).when(thisMock).thisMethodIsCalled(argument);

Unit Testing Service-Layer Methods

  • Mockito can be used to test service layer methods by mocking the repository.

  • Example:

    • BookService is tested by mocking the BookRepository.

    • when(bookRepository.findAll()).thenReturn(books); defines the behavior of the mock repository.

    • verify(bookRepository).findAll(); verifies that the findAll method was called.

ArgumentCaptor

  • ArgumentCaptor is used to capture argument values for further assertions.

  • Useful when inspecting complex objects passed to methods of mocked dependencies.

  • Example:

    • UserService depends on UserRepository.

    • ArgumentCaptor<User> userArgumentCaptor; captures the User object passed to the save method.

    • verify(userRepository).save(userArgumentCaptor.capture()); captures the argument.

    • User capturedUser = userArgumentCaptor.getValue(); retrieves the captured value.

    • Assertions are performed on the captured object.

MockMvc

  • MockMvc is part of the Spring Framework's testing suite, allowing testing of Spring MVC controllers without starting a full HTTP server.

  • It simulates HTTP requests and responses.

  • @WebMvcTest auto-configures Spring Security and MockMvc.

  • @MockitoBean provides mock objects for @Service components.

  • Example:

    • BookController is tested using MockMvc and a mock BookService.

    • mockMvc.perform(MockMvcRequestBuilders.post("/books")...) performs a request.

    • .andExpect(MockMvcResultMatchers.status().isCreated()) asserts the status code.

    • .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("The Great Gatsby")) asserts the response content.

Maven

  • Maven is a tool for building and managing Java-based projects.

  • It automates dependency management.

  • Objectives:

    • Easy build process

    • Uniform build system

    • Project quality information

    • Better development practices

  • Supports automatic source generation, compiling, packaging, and reporting.

  • Integrates with CI/CD pipelines.

  • Jenkins can use Maven as its build tool.

Installation and Configuration

  • Maven is often integrated in IDEs like IntelliJ IDEA.

  • Installation involves downloading and configuring Maven.

  • mvn -v confirms correct installation.

Maven Standard Directory Layout

  • Project base directory contains pom.xml (Project Object Model).

  • src/main contains source code and resources.

  • src/test contains unit tests.

  • target is the default output folder.

  • mvn clean deletes the contents of the target folder.

Maven Architecture

  • Central Repository (Maven community) contains common libraries.

  • Local Repository is on the developer's machine.

  • Remote Repository stores final packages.

  • Maven checks the local repository first, then downloads dependencies from the Central Repository if needed.

Maven Built-In Life Cycles

  • clean: handles cleanup.

  • default: handles build and distribution.

  • site: creates project documentation.

  • Specific goals can be run without executing the entire phase using $mvn [plugin:goal].

  • Phases are executed in a specific order, including all preceding phases.

Build Lifecycle Phases

  • validate: checks if all information is available

  • compile: compiles source code.

  • test: runs unit tests.

  • package: packages compiled source code.

  • integration-test: processes and deploys the package if needed.

  • verify: runs checks on integration test results.

  • install: installs the package to a local repository.

  • deploy: copies the package to the remote repository.

Plugins and Goals

  • Maven is a plugin execution framework.

  • Tasks are executed by goals grouped in plugins.

  • compile goal (compiler plugin) compiles source code.

  • test goal (surefire plugin) runs unit tests.

  • mvn help:describe -Dcmd=PHASENAME generates an overview of phases and goals.

  • Build plugins: executed during the build process, configured in the <build> element of pom.xml

  • Reporting plugins: executed during site generation, configured in the <reporting> element of pom.xml.

Dependencies and Scopes

  • Local Repository is a directory on the machine running Maven, (e.g., $user.home/.m2/repository).

  • Remote repositories are accessed via protocols like file:// and https://.

  • Maven Central Repository Search helps to find dependencies.

  • Direct dependencies are listed in the pom.xml under the <dependencies> section.

  • Transitive dependencies are dependencies of direct dependencies.

  • mvn dependency:tree generates the dependency tree.

  • Dependency scopes limit transitivity and determine when a dependency is included in the classpath.

Maven Commands

  • mvn -v: prints the Maven version.

  • mvn clean: clears the target directory.

  • mvn package: builds the project and packages the JAR file.

  • mvn install: builds the project and installs the artifact into the local Maven repository.

  • mvn dependency:tree: generates the dependency tree.

Maven in CI/CD Pipeline

  • Maven simplifies builds, dependency management, and testing in CI pipelines.

  • It ensures consistent builds, automates unit tests and code quality checks.

  • Maven plugins enable static code analysis, reporting, and artifact deployment.

Integrating Maven with GitHub Actions

  • GitHub Actions automates CI/CD workflows.

  • A typical workflow includes:

    • Checkout the code

    • Set up Java

    • Cache dependencies

    • Build and Test using Maven commands

    • Deploy artifacts. (OPTIONAL!)

Deploying a Maven-Based Java Application in a Docker Container

  • Docker containerization allows running applications in a consistent environment.

  • A typical Dockerfile includes:

    • Using an official OpenJDK image as the base

    • Copying the built JAR file into the container

    • Setting the working directory

    • Specifying the command to run the application

Generics

  • Java Generics provide the ability to write code that is independent of a data type.

  • Java Collections framework is entirely written generically.

Before Generics

  • Java is a strongly-typed language.

  • Type-safety was not implemented in the Java Collections framework before Java 5.

  • Since Java 5, type-safety was introduced with generic parameters like <E> in ArrayList<E>.

Writing Generic Classes

  • A generic class contains a type parameter (e.g., <T>).

  • The type parameter is used as the datatype for fields, parameters, and returntype.

  • Example:

    public class Duo<T> {
    private T first;
    private T second;
    }
    
  • When creating objects of the generic class, you indicate which actual datatype the type parameter should be replaced with (e.g., Duo<String>).

  • The compiler can infer the datatype of the objects (i.e., type inference).

Naming Conventions
  • Use a single uppercase letter to name a type parameter.

  • Common conventions:

    • E - Element (used in Java Collections Framework)

    • K - Key (used in Map)

    • N - Number

    • T - Type

    • V - Value (used in Map)

    • S, U, V etc. - use when T already in use

Generic Interfaces

Interface Comparable < T >
  • The generic interface Comparable<T> enables comparing objects.

  • Classes implementing Comparable<T> can use methods like Collections.sort(...).

  • You must implement the method int compareTo(T e).

  • Return values:

    • 0 if objects are equal.

    • -1 or negative number if this is less than the argument.

    • 1 or positive number if this is greater than the argument.

  • Keep the natural order consistent with the implementation of equals() method.

  • e1.compareTo(null) should always throw a NullPointerException.

Writing a generic interface
  • In addition to generic classes, you can also develop generic interfaces and methods.

  • Example of a generic interface with two generic parameters T and U:

    public interface Service<T,U> {
        T execute(U arg);
    }
    
  • Two classes implementing the Service interface:

    public class CountService implements Service<Integer, String> {
        @Override
        public Integer execute(String arg) {
            return arg.length();
        }
    }
    
    public class PersonConverterService implements Service<String, Person> {
        ...
        @Override
        public Person execute(String arg) {
            ...
        }
    }
    

Generic Methods

  • Methods can contain one or more generic types.

  • Even a constructor can use generic parameters.

  • Syntax: public static <T> boolean occursExactTimes(List<T> items, T item, int times)

  • The method occursExactTimes can be used with different datatypes.

Bounded Generics

  • Bounded means restricted.

  • Restriction of the datatypes that a method accepts.

  • Example: <T extends Comparable<T>>

  • The generic method allows for comparisons of any type that implements the Comparable interface.

  • Boundaries: You can include at most 1 class, there is no restriction on the number of interfaces.

  • Class must always come first in the listing: <T extends MyClass & MyFirstInterface & MySecondInterface & MyThirdInterface>.

Wildcards

  • The question mark (?) represents an unknown type.

Unbounded wildcards
  • Example: public static void printList(List<?> list)

  • You may read data with an unknown data type (thus a wildcard),

  • But you may not write the data.

Upper bound wildcards
  • public static void process(List<? extends Foo> list)

  • The upper-bounded wildcard matches Foo and any subtype of Foo

Lower bound wildcards
  • public static void addNumbers(List<? super Integer> list)

  • This allows write actions with parameter of the specified or super type

Type Erasure and Consequences

  • Generics exist only at compile time.

  • During compilation, type erasure removes all information about the generic data type.

  • List<T> is replaced by List<Object> and List<T extends Person> becomes List<Person>.

  • In Java, there will always be only one compiled class (.class file) generated for a generic class.

  • Due to type erasure you cannot create generic static variables in a class.

  • It is not possible to call a constructor of a generic datatype.

  • You cannot use instanceof operator.

Java I/O

  • File handling is a fundamental aspect of programming, allowing developers to read from, write to, and manipulate files.

  • Java provides two primary APIs: java.io package (traditional) and java.nio package (more recent).

Basics of File Handling

  • A file system manages access to both the content of files and the metadata about those files.

  • Files are stored in directories.

  • The files and directories are accessed by specifying a path.

  • Paths:

    • Absolute: relative to the file system's root directory

    • Relative: relative to some other directory. Often prefixed with one or more delimited ”..” character sequences

  • java.io.File

  • java.nio.file.Path

  • java.nio.files.Files

  • On some systems, Java can compensate for differences such as the direction of the file separator slashes in a pathname.

  • Thus, using forward slashes will make your application system independent.

Accessing directories

using the File class:

  • Check if a folder path has been provided as an argument

  • Check if the folder exists and is indeed a directory

  • List all files in the directory

using java.nio classes:

  • Path filePath = Paths.get(folderPath);

  • Using try-with-resources to ensure that the stream is closed after use.

  • The try-with-resources statement is a try statement that declares one or more resources: Any object that implements java.lang.AutoCloseable

Creating directories and files:

  • Files.createDirectory(path.getParent());

  • Files.createFile(path);

Reading small Files:

  • Files.readAllLines(path)

Copying files:

  • Files.copy(original, copy)

Byte Streams

  • A stream is an ordered sequence of bytes of an arbitrary length.

  • Bytes flow over an output stream from an application to a destination.

  • Bytes flow over an input stream from a source to an application

  • java.io package provides output stream and input stream classes that are descendants of its abstract OutputStream and InputStream classes.

  • FileOutputStream

  • FileInputStream

Character Streams

  • If you need to stream characters, you should take advantage of Java's writer and reader classes (char instead of byte).

  • Writer and reader classes take character encodings into account.

  • CharArrayWriter, StringWriter, FileWriter, OutputStreamWriter

  • Character Stream

  • BufferedReader CharArrayReader StringReader FileReader Reader InputStreamReader

  • Writer BufferedWriter CharArrayWriter StringWriterFileWriter OutputStreamWriter
    BufferedReader
    offers a simple and efficient way to read larger text files.
    BufferedReader
    groups (buffers) characters from a file so that you can read the file line by line.
    BufferedReader
    The end of the text file is reached when readLine() returns null
    BufferedWriter writes larger pieces of text to the file at once
    BufferedOutputStream
    Character sets: a charset specifies how characters are encoded into bytes and decoded back into characters.
    Files.newBufferedWriter(Paths.get("resources/myfile.txt"), Charset.forName("UTF-8"));
    BufferedOutputStream
    In UTF-8 1 to 4 bytes are used to represent the characters.
    BuffOffsetWrite
    Character Stream
    BuffOffsetWrite
    BufferedReader

Program Properties

  • In Java use java.util.Properties class to read and write configuration files. (key-value pairs)

  • Properties properties = new Properties();

  • properties.load(inputStream);

  • properties.getProperty(demo.name)

Object Serialization and Deserialization

  • Serialization: The process of converting an object’s state into a byte stream.

  • Deserialization: The reverse process, where the byte stream is converted back into a replica of the original object, restoring its state.

  • To serialize an object in Java, the class must implement the


Mockito

  • Mockito is a popular open-source framework for mocking objects in software testing. It simplifies the creation of test doubles, allowing developers to isolate the unit under test from its dependencies.

  • Mocking creates lightweight, configurable imitations of real objects. These mocks can be programmed to simulate specific behaviors and interactions, making testing more predictable and focused.

  • Mocks simulate complex, real objects (e.g., database connections, network services) when using the real ones is impractical. This is particularly useful for testing code that depends on external resources or services that are slow, unreliable, or not readily available during testing.

Mocking with Mockito
  • Mockito is used to mock objects for testing. This involves creating mock objects that mimic the behavior of real objects, allowing developers to verify interactions and isolate units of code during testing.

  • Example:

    • A MyService class depends on an OtherService interface.

    public class MyService {
        private OtherService otherService;public MyService(OtherService otherService) {
        this.otherService = otherService;
    }
    
    public int doCalculation(int input) {
        return otherService.getValue() * input;
    }
    }
    
    • Instead of using a real implementation of OtherService, a mock object is created.

    import org.junit.jupiter.api.Test;
    import org.mockito.Mock;
    import org.mockito.InjectMocks;
    import org.mockito.junit.jupiter.MockitoExtension;
    import static org.mockito.Mockito.when;
    import static org.junit.jupiter.api.Assertions.assertEquals;
    
    @ExtendWith(MockitoExtension.class)
    public class MyServiceTest {@Mock
    private OtherService otherService;
    
    @InjectMocks
    private MyService myService;
    
    @Test
    public void testDoCalculation() {
        when(otherService.getValue()).thenReturn(5);
        assertEquals(500, myService.doCalculation(100));
    }
    }
    
  • The @Mock annotation is used to create mock objects. Mockito initializes these mock objects, allowing them to be used in tests.

  • The @InjectMocks annotation is used to inject mock objects into the class being tested. This simplifies the process of wiring dependencies in the test environment.

  • when(otherService.getValue()).thenReturn(5); defines the behavior of the mock object. It specifies that when the getValue() method is called on the otherService mock, it should return the value 5.

  • assertEquals(500, myService.doCalculation(100)); asserts that the doCalculation method returns the expected value. This verifies that the interaction between MyService and OtherService behaves as expected.

Creating Fake (or Dummy) Objects
  • Mocking data objects is useful when:

    • The object is difficult to instantiate.

    • The object has behavior that's being tested elsewhere.

    • The object's state is irrelevant to the test's outcome.

    @Test
    public void testCreateFakeObject() {
        // Mocking a data object
        BookRepository mockBookRepository = Mockito.mock(BookRepository.class);

        // Stubbing the mock to return a specific book when findById is called
        Book fakeBook = new Book("123", "The Lord of the Rings", "J.R.R. Tolkien");
        Mockito.when(mockBookRepository.findById("123")).thenReturn(Optional.of(fakeBook));

        // Using the mock in a service layer test
        BookService bookService = new BookService(mockBookRepository);
        Optional<Book> retrievedBook = bookService.findBookById("123");

        // Assertions
        assertTrue(retrievedBook.isPresent());
        assertEquals("The Lord of the Rings", retrievedBook.get().getTitle());
    }
  • Stubbing is configuring the mock and defining what to do when specific methods are called. It helps in controlling the behavior of the mock object during the test.

  • Two ways of stubbing:

    • when(thisMethodIsCalled).thenReturn(something);

    • doReturn(something).when(thisMock).thisMethodIsCalled(argument);

    @Test
    public void testStubbingMethods() {
        // Mocking a List
        List<String> mockedList = Mockito.mock(List.class);

        // Using when(...).thenReturn(...)
        Mockito.when(mockedList.get(0)).thenReturn("First element");
        assertEquals("First element", mockedList.get(0));

        // Using doReturn(...).when(...).thisMethodIsCalled(...)
        Mockito.doReturn("Second element").when(mockedList).get(1);
        assertEquals("Second element", mockedList.get(1));
    }
  • For mocking void methods, use doNothing().when(thisMock).thisMethodIsCalled(argument);

    • or doThrow(Exception.class).when(thisMock).thisMethodIsCalled(argument);

    @Test
    public void testMockingVoidMethods() {
        // Mocking a Printer
        Printer mockPrinter = Mockito.mock(Printer.class);

        // Using doNothing().when(...).thisMethodIsCalled(...)
        Mockito.doNothing().when(mockPrinter).print("Hello, Mockito!");

        // Execute the method (no exception should be thrown)
        mockPrinter.print("Hello, Mockito!");

        // Verify that the method was called
        Mockito.verify(mockPrinter).print("Hello, Mockito!");

        // Using doThrow(...).when(...).thisMethodIsCalled(...)
        Mockito.doThrow(new RuntimeException("Printer jam!")).when(mockPrinter).print("Error!");

        // Execute the method and expect an exception
        assertThrows(RuntimeException.class, () -> mockPrinter.print("Error!"));
    }
Unit Testing Service-Layer Methods
  • Mockito can be used to test service layer methods by mocking the repository. It enables developers to isolate the service layer logic and verify its interactions with the data access layer.

  • Example:- BookService is tested by mocking the BookRepository.

    import org.junit.jupiter.api.Test;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.Mockito;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.util.Arrays;
    import java.util.List;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.mockito.Mockito.verify;
    import static org.mockito.Mockito.when;
    
    @SpringBootTest
    public class BookServiceTest {@Mock
    private BookRepository bookRepository;
    
    @InjectMocks
    private BookService bookService;
    
    @Test
    public void testFindAllBooks() {
        // Arrange
        List&lt;Book&gt; books = Arrays.asList(
                new Book("1", "The Great Gatsby", "F. Scott Fitzgerald"),
                new Book("2", "To Kill a Mockingbird", "Harper Lee")
        );
        when(bookRepository.findAll()).thenReturn(books);
    
        // Act
        List&lt;Book&gt; result = bookService.findAllBooks();
    
        // Assert
        assertEquals(2, result.size());
        verify(bookRepository).findAll();
    }
    }
    
    • when(bookRepository.findAll()).thenReturn(books); defines the behavior of the mock repository. It specifies that when the findAll() method is called on the bookRepository mock, it should return the list of books.

    • verify(bookRepository).findAll(); verifies that the findAll method was called. This ensures that the service layer method interacts with the repository as expected.

ArgumentCaptor
  • ArgumentCaptor is used to capture argument values for further assertions. It allows developers to inspect the values passed to methods of mocked dependencies.

  • Useful when inspecting complex objects passed to methods of mocked dependencies. This is particularly helpful when the state of the object being passed is important for the test.

  • Example:- UserService depends on UserRepository.

    import org.junit.jupiter.api.Test;
    import org.mockito.ArgumentCaptor;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.Mockito;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.mockito.Mockito.verify;
    
    @SpringBootTest
    public class UserServiceTest {@Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    public void testSaveUser() {
        // Arrange
        User user = new User("1", "John Doe", "john.doe@example.com");
        ArgumentCaptor&lt;User&gt; userArgumentCaptor = ArgumentCaptor.forClass(User.class);
    
        // Act
        userService.saveUser(user);
    
        // Assert
        verify(userRepository).save(userArgumentCaptor.capture());
        User capturedUser = userArgumentCaptor.getValue();
        assertEquals("John Doe", capturedUser.getName());
        assertEquals("john.doe@example.com", capturedUser.getEmail());
    }
    }
    
    • ArgumentCaptor<User> userArgumentCaptor; captures the User object passed to the save method.

    • verify(userRepository).save(userArgumentCaptor.capture()); captures the argument.

    • User capturedUser = userArgumentCaptor.getValue(); retrieves the captured value.

    • Assertions are performed on the captured object.

MockMvc
  • MockMvc is part of the Spring Framework's testing suite, allowing testing of Spring MVC controllers without starting a full HTTP server. It provides a way to simulate HTTP requests and responses for testing purposes.

  • It simulates HTTP requests and responses. This makes it possible to test the controller logic in isolation.

  • @WebMvcTest auto-configures Spring Security and MockMvc.

  • @MockitoBean provides mock objects for @Service components. It is used to inject mock service components into the Spring context.

  • Example:- BookController is tested using MockMvc and a mock BookService.

    import org.junit.jupiter.api.Test;
    import org.mockito.Mockito;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    import org.springframework.boot.test.mock.mockito.MockBean;
    import org.springframework.http.MediaType;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
    import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
    
    @WebMvcTest(BookController.class)
    public class BookControllerTest {@Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private BookService bookService;
    
    @Test
    public void testCreateBook() throws Exception {
        Mockito.when(bookService.createBook(Mockito.any())).thenReturn(new Book("1", "The Great Gatsby", "F. Scott Fitzgerald"));
    
        mockMvc.perform(MockMvcRequestBuilders.post("/books")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"title\": \"The Great Gatsby\", \"author\": \"F. Scott Fitzgerald\"}"))
                .andExpect(MockMvcResultMatchers.status().isCreated())
                .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("The Great Gatsby"));
    }
    }
    
    • mockMvc.perform(MockMvcRequestBuilders.post("/books")...) performs a request.

    • .andExpect(MockMvcResultMatchers.status().isCreated()) asserts the status code.

    • .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("The Great Gatsby")) asserts the response content.