Smoke Testing vs Load Testing vs Stress Testing vs Spike Testing vs Soak Testing
Smoke Testing: A preliminary test to ensure the most important functions of an application work correctly. The term originated in hardware and electronics: engineers would power on a new circuit or device, and if it literally started smoking, they knew something was fundamentally wrong

Why Smoke Testing: Prevents wasting time on deeper tests when the basic functionality is already broken. Test Basic Functional Requirements are met. I.E. Can you log in, view your balance, and make a simple transaction

Load Testing: Must simulate realistic traffic patterns. The goal of load testing is to verify that a system can handle expected user traffic and workload without performance degradation, downtime, or failures. It ensures reliability, responsiveness, and stability under real-world usage conditions.

During normal days, it handles ~10,000 users.A load test simulates this traffic to check if pages load quickly, payments process smoothly, and servers remain stable.If response times spike or errors occur, developers know where to optimize.

Load testing is like a rehearsal for your application for normal day traffic

Stress Testing:The goal of stress testing is to evaluate how a system behaves under extreme or abnormal conditions—pushing it beyond its normal operating limits to identify breaking points, bottlenecks, and ensure the system fails gracefully rather than catastrophically.

Stress testing is about resilience. It ensures that when systems face extreme, unexpected loads, they don’t collapse outright but instead degrade gracefully, recover quickly, and protect data integrity.

Stress testing is like a rehearsal for your application for maximum capacity before performance collapses.

Soak Testing(Endurance Test):The goal of a soak test (also called endurance testing) is to verify that a system can handle a sustained workload over an extended period of time without performance degradation, resource leaks, or failures.

Soak testing is about endurance. It ensures that your system doesn’t just work well in the short term but remains stable, efficient, and reliable over extended periods of continuous use

Long-running tests reveal issues like memory leaks, file handle leaks, or database connection leaks that don’t show up in short tests.

Spike Testing:The goal is to evaluate how a system reacts to sudden, extreme increases (or decreases) in load. It checks whether the system can handle abrupt traffic surges, recover quickly, and remain stable without crashing or degrading severely.

Soak testing is about endurance. It ensures that your system doesn’t just work well in the short term but remains stable, efficient, and reliable over extended periods of continuous use

Reveals bottlenecks in servers, databases, or network components that only appear during sudden surges. validate Scalability & Elasticity

Sanity Testing: Sanity Testing is narrow, deep, correctness check of specific functionality whereas smoke test is to verify that the basic, critical functionalities of a build work before deeper testing. road and shallow – covers major features at a high level.

  1. Is it right to use @SpringBootTest (or) @ContextConfiguration in Unit Test?
    Starting spring for each test is a very expensive operation. It’s not a unit test anymore rather integration test. @SpringBootTest goes further and tries to mimic the processes added by Spring Boot framework for creating the context: Decides what to scan based on package structures, loads external configurations from predefined locations optionally runs autoconfiguration starters and so on and so forth. @SpringBootTest loads the necessary beans and inject into each other.
    When using @ContextConfiguration you present way to filter what exactly should be run, what beans to load and to inject into each other.
  2. How to Optimize the Unit test by selectively loading the resource?
    You can selectively load the resource during unit test

    • To test your Respository : use @DataJpaTest annotation for test slice.
    • To test your Service layer : use JUnit and Mockito. Here you will mock your Repository
    • To test your Controller layer : use @WebMvcTest annotation for test slice or use JUnit and Mockito. Here you will mock your Service in both cases
    • To test a Component, such as a third party library wrapper or load some specific beans: use @ExtendWith(SpringExtension.class) and @ContextConfiguration/@Import or @SpringJUnitWebConfig which is the combinaison of the both.
    • To do an integration test : use @SpringBootTest
  3. When to use @SpringBootTest and @ExtendWith(SpringExtension.class)
    Use @ExtendWith(SpringExtension.class) without loading the entire application context that is included with @SpringBootTest. As you say it’s a lighter weight approach if you just want to mock beans with @MockBean. @SpringBootTest does in fact include @ExtendWith(SpringExtension.class)

    Use @SpringBootTest when,

    • You need to test the application as a whole, including multiple layers like the web layer, service layer, and repository layer.
    • You require an embedded server to run the test, such as when testing web controllers.
    • You want to verify the integration of all components in a fully loaded application context.

    Use SpringRunner/@ExtendWith(SpringExtension.class) when,

    • You are writing unit tests or lightweight integration tests that only need certain features of Spring, such as dependency injection or transaction management, without loading the full application context.
    • You want to test specific layers (e.g., service layer) in isolation without the overhead of starting the entire Spring application.

BaseUnitTest.java

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

import java.util.concurrent.TimeUnit;

public class BaseUnitTest {
    long startTime;
    long endTime;

    @BeforeEach
    public void recordStartTime() {
        startTime = System.currentTimeMillis();
    }

    @AfterEach
    public void recordEndAndExecutionTime() {
        endTime = System.currentTimeMillis();
        System.out.println("Last testcase exection time in millisecond : " + 
                              TimeUnit.NANOSECONDS.toMicros((endTime - startTime)) + " Seconds");
    }
}

FirstTest.java

import org.junit.jupiter.api.Test;

class FirstTest extends BaseUnitTest{
    @Test
    void oneSecondTest() throws InterruptedException {
        System.out.println("oneSecondTest() name  =>  " + Thread.currentThread().getName());
        Thread.sleep(1000);
    }

    @Test
    void twoSecondTest() throws InterruptedException {
        System.out.println("twoSecondTest() name => " + Thread.currentThread().getName());
        Thread.sleep(2000);
    }

    @Test
    void threeSecondTest() throws InterruptedException {
        System.out.println("threeSecondTest() name => " + Thread.currentThread().getName());
        Thread.sleep(3000);
    }
}

SecondTest.java

import org.junit.jupiter.api.Test;

public class SecondTest extends BaseUnitTest{
    @Test
    void oneSecondTest() throws InterruptedException {
        System.out.println("oneSecondTest() name => " + Thread.currentThread().getName());
        Thread.sleep(1000);
    }

    @Test
    void twoSecondTest() throws InterruptedException {
        System.out.println("twoSecondTest() name => " + Thread.currentThread().getName());
        Thread.sleep(2000);
    }

    @Test
    void threeSecondTest() throws InterruptedException {
        System.out.println("threeSecondTest() name => " + Thread.currentThread().getName());
        Thread.sleep(3000);
    }
}

ThirdTest.java

import org.junit.jupiter.api.Test;

public class ThirdTest extends BaseUnitTest{
    @Test
    void oneSecondTest() throws InterruptedException {
        System.out.println("oneSecondTest() name => " + Thread.currentThread().getName());
        Thread.sleep(1000);
    }

    @Test
    void twoSecondTest() throws InterruptedException {
        System.out.println("twoSecondTest() name => " + Thread.currentThread().getName());
        Thread.sleep(2000);
    }

    @Test
    void threeSecondTest() throws InterruptedException {
        System.out.println("threeSecondTest() name => " + Thread.currentThread().getName());
        Thread.sleep(3000);
    }
}

Output

Last testcase exection time in millisecond : 0 Seconds
Last testcase exection time in millisecond : 2 Seconds
Last testcase exection time in millisecond : 2 Seconds
Last testcase exection time in millisecond : 2 Seconds
Last testcase exection time in millisecond : 3 Seconds
Last testcase exection time in millisecond : 3 Seconds
Last testcase exection time in millisecond : 3 Seconds
threeSecondTest() name => ForkJoinPool-1-worker-10
twoSecondTest() name => ForkJoinPool-1-worker-7
oneSecondTest() name  =>  ForkJoinPool-1-worker-1
twoSecondTest() name => ForkJoinPool-1-worker-6
oneSecondTest() name => ForkJoinPool-1-worker-3
threeSecondTest() name => ForkJoinPool-1-worker-5
oneSecondTest() name => ForkJoinPool-1-worker-4
twoSecondTest() name => ForkJoinPool-1-worker-8
threeSecondTest() name => ForkJoinPool-1-worker-9

junit-platform.properties

# Enable parallelism
junit.jupiter.execution.parallel.enabled = true

# Enable parallelism for both test methods and classes
junit.jupiter.execution.parallel.mode.default =  concurrent

In Maven Command

>>mvn test -Djunit.jupiter.execution.parallel.enabled=true  -Djunit.jupiter.execution.parallel.mode.default=concurrent

Four possibilities in junit-platform.properties config

//Only One class and One method in that class would be executed - Sequential Execution
junit.jupiter.execution.parallel.mode.default =  same_thread
junit.jupiter.execution.parallel.mode.classes.default = same_thread

//More than one class would run in parallel but only one method in each class would be executed 
//Test method within one class run sequentially but more than one class run in parallel
junit.jupiter.execution.parallel.mode.default =  same_thread
junit.jupiter.execution.parallel.mode.classes.default = concurrent

//Only One class and Multiple method in that class would be executed
junit.jupiter.execution.parallel.mode.default =  concurrent
junit.jupiter.execution.parallel.mode.classes.default = same_thread

//Multiple classes and Multiple method in  those classes would be executed
junit.jupiter.execution.parallel.mode.default =  concurrent
junit.jupiter.execution.parallel.mode.classes.default = concurrent

Below is a simple class which uses stub for Testing

  1. Below is a simple business implementation class which EmployeeDetailsImpl which uses EmployeeService API methods
  2. Now to test EmployeeDetailsImpl we need to substitude the methods in service class with stub methods for which we create EmployeeServiceStub
  3. EmployeeServiceStub is a stub class which implements the EmployeeService and provide method definitions
  4. EmployeeDetailsImplTest is the test class for EmployeeDetailsImpl which performs the unittest for methods in EmployeeDetailsImpl

EmployeeService.java

 
import java.util.List;

public interface EmployeeService {
    public List<String> GetEmployeeDetails();
}

EmployeeDetailsImpl.java

 
import com.mugil.org.api.EmployeeService;

import java.util.List;

public class EmployeeDetailsImpl {
    EmployeeService employeeService;

    public EmployeeDetailsImpl(EmployeeService pEmployeeService){
        this.employeeService = pEmployeeService;
    }

    public List<String> getEmployeeList(){
        List<String> arrEmp = this.employeeService.GetEmployeeDetails();
        return arrEmp;
    }
}

EmployeeServiceStub.java

 
import java.util.Arrays;
import java.util.List;

public class EmployeeServiceStub  implements  EmployeeService{

    @Override
    public List<String> GetEmployeeDetails() {
        return Arrays.asList("Mugil", "Mani", "Vinu");
    }
}

EmployeeDetailsImplTest.java

 
import com.mugil.org.api.EmployeeServiceStub;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class EmployeeDetailsImplTest {
    @Test
    public void getEmployeeList_success(){
        EmployeeServiceStub employeeService = new  EmployeeServiceStub();
        EmployeeDetailsImpl employeeDetailsImpl = new EmployeeDetailsImpl(employeeService);
        assertEquals(3, employeeDetailsImpl.getEmployeeList().size());
    }
}

Dis-advantage of the above implementation

  1. In the above implementation when ever new method is added to the API interface, it should be added to the EmployeeServiceStub which implements EmployeeService
  2. EmployeeServiceStub is extra java file which should be taken care, incase there are multiple services used in class multiple service stub needs to be created

Replacing stub with mocks
In the below code, the same stub service(EmployeeServiceStub.java) is replaced with mock class and its methods are replaced using when and return.This prevents the need for another class

EmployeeDetailsImplTest.java

 
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class EmployeeDetailsImplTest {
    @Test
    public void getEmployeeList_success(){
     
        //Replacement code for Stub class
        EmployeeService employeeService = mock(EmployeeService.class);
        when(employeeService.GetEmployeeDetails()).thenReturn(Arrays.asList("Mugil", "Mani", "Vinu"));

        EmployeeDetailsImpl employeeDetailsImpl = new EmployeeDetailsImpl(employeeService);
        assertEquals(3, employeeDetailsImpl.getEmployeeList().size());
    }
}

In the below code we are going to mock the List Interface and override the method behaviour of List Methods
Methods mocked

  1. get(index)
  2. size()
  3. exception

ListTest.java

package com.mugil.org;

import org.junit.Before;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.junit.Assert.assertEquals;

public class ListTest {
    List arrEmployee;

    @Before
    public void init(){
        arrEmployee = mock(List.class);
    }

    @Test
    public void ListMock_SizeMethod(){
        when(arrEmployee.size()).thenReturn(3);
        assertEquals(3, arrEmployee.size());
    }

    @Test
    public void ListMock_GetMethod(){
        when(arrEmployee.get(0)).thenReturn("Employee1");
        when(arrEmployee.get(1)).thenReturn("Employee2");
        when(arrEmployee.get(2)).thenReturn("Employee3");

        assertEquals("Employee2", arrEmployee.get(1));
        assertNotEquals(null, arrEmployee.get(2));
    }

    @Test(expected = RuntimeException.class)
    public void ListMock_ThrowException(){
        when(arrEmployee.get(anyInt())).thenThrow(new RuntimeException());
        arrEmployee.get(1);
    }
}

Junit 5 = Platform + Jupiter + Vintage 
  1. Platform = Engine + Runner + Launcher
  2. All the Class files needed for Coding Test Cases
  3. Provides Support for Junit3 and 4

Adding Dependency in pom.xml for Junit 5

JUnit 5 Platform
includes junit-jupiter-api + junit-platform-engine

<dependencies>
    [...]
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>
    [...]
</dependencies>

If you want to write and execute JUnit 3 or 4 tests via the JUnit Platform add the Vintage Engine to the dependencies

<dependencies>
    [...]
    <dependency>
        <groupId>org.junit.vintage</groupId>
        <artifactId>junit-vintage-engine</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>
    [...]
</dependencies>
  1. @BeforeAll, @AfterAll – Runs Before and After Class and should be static
  2. @DisplayName – Displays description about the Test Method
  3. @Disabled – @Ignore in Junit 4. Disables the Test Method
  4. @Nested – Helps in grouping of similar test methods together
  5. @ParameterizedTest – Supplying more than one input for same method in row using array
  6. @ValueSource – Provides multiple paramters to same test method for ParameterizedTest
  7. @CsvSource – Provides multiple paramters to same test method for ParameterizedTest in Key Value Pair. Key is Input and Value is expected Output

AccountUtils.java

import java.io.IOException;

public class AccountUtils {

    public boolean validateAccountId(String acctNo) throws IOException {
        if (getAccountIDLength(acctNo)) {
            return true;
        } else {
            throw new IOException("Account ID is Invalid");
        }
    }

    public boolean getAccountIDLength(String acctNo)
    {
        if(acctNo.length() < 5) return false;
        if(acctNo.length() > 10) return false;
        if(!acctNo.contains("-")) return false;

        return true;
    }

    public  String getFormattedAccID(String accountNo){
        return accountNo.toUpperCase();
    }

    public String[] getBankDetails(String accountNo){
        String[] arrAccountDetails = accountNo.split("-");
        return arrAccountDetails;
    }
}

AccountUtilsTest.java

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.io.IOException;

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

public class AccountUtilsTest {

    AccountUtils systemUnderTest = new AccountUtils();

    @BeforeAll
    static void init(){
        System.out.println("Initilize connection to Database");
    }

    @AfterAll
    static void destroy(){
        System.out.println("Deallocate connection to Database");
    }

    @Test
    @DisplayName("AccountID Length Validation Success")
    void test_getAccountIDLength_Success(){
        boolean expectedOutput = true;
        boolean actualOutput = systemUnderTest.getAccountIDLength("SBI-104526");

        Assertions.assertEquals(expectedOutput, actualOutput);
        Assertions.assertTrue(actualOutput);
    }

    @Test
    @Disabled
    void test_validateAccountId_Success() throws IOException {
        Assertions.assertTrue(systemUnderTest.validateAccountId("SBI-104526"));
    }

    @Test
    @DisplayName("AccountID is Invalid ")
    void test_validateAccountId_Exception(){
        Assertions.assertThrows(IOException.class, ()->{
            systemUnderTest.validateAccountId("");
        });
    }

    @Test
    @DisplayName("Account Bank Details Validation")
    void test_AccountIDForBankName_Success(){
        String[] expectedOutput = new String[]{"SBI", "104526"};
        String[] actualOutput = systemUnderTest.getBankDetails("SBI-104526");

        Assertions.assertArrayEquals(expectedOutput, actualOutput);
    }

    @Nested
    class checkValidAccountId
    {
        @ParameterizedTest
        @ValueSource(strings={"SBI", "SBI-123456789", " ", ""})
        @DisplayName("Parameterized Test AccountID Length Validation Failiure")
        void test_getAccountIDLength_Failiure(String accountId){
            Assertions.assertFalse(systemUnderTest.getAccountIDLength(accountId));
        }

        @ParameterizedTest(name="AccountID {0} formatted to {1}")
        @CsvSource(value={"sbi-123456, SBI-123456", "cbi-123456, CBI-123456"})
        @DisplayName("AccountID Format Validation")
        void getFormattedAccID_Success(String inputString, String expectedOutput){
            Assertions.assertEquals(expectedOutput, systemUnderTest.getFormattedAccID(inputString));
        }
    }
}

What is BDD?

  Given some preconditions (Arrange) -> When an action occurs (Act) -> Then verify the output (Assert)

I.E.

  Given three items in Cart
  When one Item is deleted
  Then Two items should be left in Cart
@ExtendWith(MockitoExtension.class)
public class EmpServiceImplTest {

    @Mock
    EmployeeDB employeeDB;

    @InjectMocks
    EmpServiceImpl empServiceImpl;

    @Test
    @DisplayName("Check Employee in BDD Style")
    public void test_getEmployees_Success(){
        //Given - Employee List has only one row with List size as 1
        //When  - New Employee is added to the List
        //Then  -  Employee List should contain two rows

        //Given - Employee List has 3 Employees with List size as 3
        List<Employee> arrEmployees = new ArrayList<>();
        arrEmployees.add(new Employee("101", "Mugil"));
        arrEmployees.add(new Employee("102", "Max"));
        arrEmployees.add(new Employee("103", "Mani"));

        given(employeeDB.getEmployeesFromDB()).willReturn(arrEmployees);

        //When
        List<Employee> arrFilteredEmps = empServiceImpl.getEmployees();

        //Then - there should 3 EmployeegetEmployeeByIdFromDB
        Assertions.assertEquals(3, arrFilteredEmps.size());
    }
}
  1. What is Difference between @Injectmock and @Mock?
    @Mock creates a mock. @InjectMocks creates an instance of the class and injects the mocks that are created with the @Mock (or @Spy) annotations into this instance.
    Note you must use @RunWith(MockitoJUnitRunner.class) to initialize these mocks and inject them (JUnit 4).With JUnit 5, you must use @ExtendWith(MockitoExtension.class).

    @RunWith(MockitoJUnitRunner.class) // JUnit 4
    // @ExtendWith(MockitoExtension.class) for JUnit 5
    public class SomeManagerTest {
    
        @InjectMocks
        private SomeManager someManager;
    
        @Mock
        private SomeDependency someDependency; // this will be injected into someManager
     
         //tests...
    }
    

  2. When should I use spy vs Mock?
    Use @Mock when you want to just test the functionality externally without actually calling that method. Use @Spy when you want to test the functionality externally + internally with the very method being called. Spy is a combination of Mock(methodInterceptors) and InjectMock(Instance of class Tested).
  3. What happens when unstubbed method called?
    Mockito will return null or primitive value or empty collection
  4. Does mockito when accepts combination of specific and generic matchers?
    No. Mockito matchers should be either generic or specific.
  5. What is BDD?
    Behaviour Driven Development (Given Scenario -> When the action happens -> Then this should be the case)

    Given 3 Items in Cart ->  When One Item gets Deleted -> Then 2 Items would be in Cart
    
  6. How to Verify whether the Service or method call has been made?
    Verify is used to check whether the call to the method is done or not.

    .
    .
     Mockito.verify(employeeDB, times(1)).getEmployeeByIdFromDB("101");
     Mockito.verify(employeeDB, never()).getEmployeesFromDB();
     Mockito.verify(employeeDB, atleast(2)).getEmployeeByIdFromDB("101");
    .
    .
    
  7. How to check the arguments passed to the method?
    ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
    verify(mock).doSomething(argument.capture());
    assertEquals("John", argument.getValue().getName());
    

    (or)

    //capturing varargs:
    ArgumentCaptor<Person> varArgs = ArgumentCaptor.forClass(Person.class);
    verify(mock).varArgMethod(varArgs.capture());
    List expected = asList(new Person("John"), new Person("Jane"));
    assertEquals(expected, varArgs.getAllValues());
    
  8. @SpringBootTest vs @runwith(springrunner.class) or @ExtendWith
    Enables spring boot features like @Autowire, @MockBean etc.. during junit testing. This is used to provide a bridge between Spring Boot test features and JUnit. Whenever we are using any Spring Boot testing features in our JUnit tests, this annotation will be required.@RunWith is an old annotation from JUnit 4 to use test runners. If you’re using JUnit 5 (Jupiter), you should use @ExtendWith to use JUnit extensions

    @SpringBootTest : This annotation is used to load complete application context for end to end integration testing.The @SpringBootTest annotation can be used when we need to bootstrap the entire container. The annotation works by creating the ApplicationContext that will be utilized in our tests. So this would be the annotation used while testing bootstrap class of Spring Boot Application. load application.properties and give me all the Spring Boot goodness

    We pass list of classes as parameter to @SpringBootTest whose configs are loaded from application.properties when the application is bootstrapped.

    RestWithSpringBootApplicationTests.java

    @SpringBootTest(classes = {RestWithSpringBootApplication.class, EmployeeMgmtConfig.class})
    class RestWithSpringBootApplicationTests {
      @Test
      void contextLoads() {
      }
    }
    
  9. How Methods are mocked Internally?
    Mockito works by storing and retrieving method invocation details on mocks using method interception
    When we invoke the when() method we are in fact recalling the last registered method call from that context, In our case it is Dep2Method() is saved and returned.The recorded behaviour is played back when our test invokes the mocked method again which triggers the interceptor to recall the return value we provided.In Mockito source code, we see when method does not use the parameter methodCall. But it tries to get the OngoingStubbing from an instance of MockingProgress. This OngoingStubbing is nothing but mocking context above.

     @Test
        void doIt() {
            ClassADep1.Dep2Method();
            Mockito.when("Some Blah Blah").thenReturn("Mocked!");
            assertEquals(objTarget.doIt("Sample"), "Mocked!");
        }
    
  10. What is the difference between @ExtendWith(SpringExtension.class) and @ExtendWith(MockitoExtension.class)?
    If you want to use Spring test framework features in your tests like for example @MockBean, then you have to use @ExtendWith(SpringExtension.class). It replaces the deprecated JUnit4 @RunWith(SpringJUnit4ClassRunner.class)

    When NOT involving Spring – If you just want to involve Mockito and don’t have to involve Spring, for example, when you just want to use the @Mock / @InjectMocks annotations, then you want to use @ExtendWith(MockitoExtension.class), as it doesn’t load in a bunch of unneeded Spring stuff. It replaces the deprecated JUnit4 @RunWith(MockitoJUnitRunner.class).

  11. When to use @Extendwith and @Runwith?
    If you are using Junit version < 5, so you have to use @RunWith(SpringRunner.class) or @RunWith(MockitoJUnitRunner.class) etc.If you are using Junit version = 5, so you have to use @ExtendWith(SpringExtension.class) or @ExtendWith(MockitoExtension.class) Junit4 uses *Runner.class in annotation with @RunWith

    SpringRunner.class and MockitoJunitRunner.class for Junit4
    SpringExtension.class and MockitoExtension.class for Junit5
    

Below we have a Student class with Private Class Variable(id and name), Service and Private Methods.

Student.java

public class Student {
    private Integer id;
    private String name;

    @Autowired
    private StudentService studentService;

    private String getStudentDetails(){
      return "id: " + getId() + "; name: " + getName();
    }  
}

Mocking a Private Field
we cannot access the private field id to assign a value for testing, because there isn’t a public setter method for it.
We can then use ReflectionTestUtils.setField method to assign a value to the private member id

StudentTest.java

@Test
public void setValueWithNoSetter() {
    Student student = new Student();
    ReflectionTestUtils.setField(student, "id", 101); 
    assertTrue(student.getId().equals(101));
}

Mocking a Private Method
In a similar way we can invoke private method – getStudentDetails() as below in student class
StudentTest.java

@Test
public void whenNonPublicMethod_thenReflectionTestUtilsInvokeMethod() {
    Student student= new Student ();
    ReflectionTestUtils.setField(student, "id", 101);
    ReflectionTestUtils.setField(student, "name", "Mugil");
 
    assertTrue(ReflectionTestUtils.invokeMethod(student, "employeeToString").equals
                 ("id: 101; name: Mugil"));
}

Mocking a Private Dependencies
StudentTest.java

@Test
public void whenNonPublicMethod_thenReflectionTestUtilsInvokeMethod() 
{
   Student student = new Student();
   StudentService studService = mock(StudentService.class);
   when(studService.getStudentStatus(student.getId())).thenReturn("Active");

   ReflectionTestUtils.setField(student, "studentService", studService);

.
.
}