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
MyServiceclass depends on anOtherServiceinterface.Instead of using a real implementation of
OtherService, a mock object is created.
The
@Mockannotation is used to create mock objects.The
@InjectMocksannotation 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 thedoCalculationmethod 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:
BookServiceis tested by mocking theBookRepository.when(bookRepository.findAll()).thenReturn(books);defines the behavior of the mock repository.verify(bookRepository).findAll();verifies that thefindAllmethod was called.
ArgumentCaptor
ArgumentCaptoris used to capture argument values for further assertions.Useful when inspecting complex objects passed to methods of mocked dependencies.
Example:
UserServicedepends onUserRepository.ArgumentCaptor<User> userArgumentCaptor;captures theUserobject passed to thesavemethod.verify(userRepository).save(userArgumentCaptor.capture());captures the argument.User capturedUser = userArgumentCaptor.getValue();retrieves the captured value.Assertions are performed on the captured object.
MockMvc
MockMvcis 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.
@WebMvcTestauto-configures Spring Security andMockMvc.@MockitoBeanprovides mock objects for@Servicecomponents.Example:
BookControlleris tested usingMockMvcand a mockBookService.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 -vconfirms correct installation.
Maven Standard Directory Layout
Project base directory contains
pom.xml(Project Object Model).src/maincontains source code and resources.src/testcontains unit tests.targetis the default output folder.mvn cleandeletes 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 availablecompile: 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.
compilegoal (compiler plugin) compiles source code.testgoal (surefire plugin) runs unit tests.mvn help:describe -Dcmd=PHASENAMEgenerates an overview of phases and goals.Build plugins: executed during the build process, configured in the
<build>element of pom.xmlReporting 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.xmlunder the<dependencies>section.Transitive dependencies are dependencies of direct dependencies.
mvn dependency:treegenerates 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>inArrayList<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- NumberT- TypeV- Value (used in Map)S, U, V etc.- use whenTalready in use
Generic Interfaces
Interface Comparable < T >
The generic interface
Comparable<T>enables comparing objects.Classes implementing
Comparable<T>can use methods likeCollections.sort(...).You must implement the method
int compareTo(T e).Return values:
0if objects are equal.-1or negative number ifthisis less than the argument.1or positive number ifthisis greater than the argument.
Keep the natural order consistent with the implementation of
equals()method.e1.compareTo(null)should always throw aNullPointerException.
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
occursExactTimescan 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
Comparableinterface.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 byList<Object>andList<T extends Person>becomesList<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.iopackage (traditional) andjava.niopackage (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
MyServiceclass depends on anOtherServiceinterface.
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
@Mockannotation is used to create mock objects. Mockito initializes these mock objects, allowing them to be used in tests.The
@InjectMocksannotation 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 thegetValue()method is called on theotherServicemock, it should return the value5.assertEquals(500, myService.doCalculation(100));asserts that thedoCalculationmethod returns the expected value. This verifies that the interaction betweenMyServiceandOtherServicebehaves 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:-
BookServiceis tested by mocking theBookRepository.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<Book> 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<Book> 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 thefindAll()method is called on thebookRepositorymock, it should return the list of books.verify(bookRepository).findAll();verifies that thefindAllmethod was called. This ensures that the service layer method interacts with the repository as expected.
ArgumentCaptor
ArgumentCaptoris 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:-
UserServicedepends onUserRepository.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<User> 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 theUserobject passed to thesavemethod.verify(userRepository).save(userArgumentCaptor.capture());captures the argument.User capturedUser = userArgumentCaptor.getValue();retrieves the captured value.Assertions are performed on the captured object.
MockMvc
MockMvcis 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.
@WebMvcTestauto-configures Spring Security andMockMvc.@MockitoBeanprovides mock objects for@Servicecomponents. It is used to inject mock service components into the Spring context.Example:-
BookControlleris tested usingMockMvcand a mockBookService.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.