Search code examples
javaspringjunitmockitospy

Mockito/Spy not working, executing the original function


I am having a class :

GP_CategoryService.java Function -->

public JSONObject deleteCategory(GP_CategorySubcategoryBean bean) {
        JSONObject data = new JSONObject();
        DirectoryCategoryMaster oCategory = getCategoryMaster(bean);

        if (oCategory.getDirCategoryId() != null) {
            boolean isDeleted = delete(oCategory);
            data.put(ConstantUtil.STATUS, ConstantUtil.SUCCESS);
            data.put(ConstantUtil.DATA, "Category deleted successfully");
        } 
    }

I have 2 inner function calls :

  1. getCategoryMaster(bean)
  2. delete(oCategory)

These are basically DAO calls, updating the Database directly. Now I want to mock these 2 fucntions alone such that whenever my test function is running, it should return true.

I have written my test function as below :

@Test
    public void deleteCategoryTestDAOV() {
        JSONObject expected = new JSONObject();
        expected.put(ConstantUtil.STATUS, ConstantUtil.SUCCESS);
        expected.put(ConstantUtil.DATA, "Category deleted successfully");
        
        bean.setCategoryId(1);
        bean.setCategoryName("Test");
        DirectoryCategoryMaster master=new DirectoryCategoryMaster();
        master.setDirCategoryId(1);
        GP_CategoryService mock = spy(new GP_CategoryService());
        when(mock.delete(master)).thenReturn(true);
        when(mock.getCategoryMaster(bean)).thenReturn(master);
        JSONObject actual=new JSONObject();
        actual=mock.deleteCategory(bean);
        assertEquals(expected.toJSONString(), actual.toJSONString());   
    }

But when I am running the test class, its executing the actual fucntion, mock is not working. Can anhyone please help me to resolve this issue?

Thanks in advance!


Solution

  • Mockito works by creating a CGLIB proxy of the class under mock. For example, if you mock(MyClass.class), you will get a dynamic proxy of class MyClass$$EnhancedByMockito$$.class, which extends MyClass.class. Since the proxy is a subclass of MyClass, the resulting class allows the user to override specific functionality ("mock" it). Most of the method interception is hidden behind the scenes for you by easy-to-use statements like when.

    Unfortunately, due to limitations in Java, it is not possible to proxy internal method calls with native reflection. This is because, if you're directly calling instance method MyClass:b from instance method MyClass::a, there is no gap to insert a proxy class which extends MyClass to execute MyClass::b indirectly.

    Technically, it's possible to do this with some really advanced bytecode manipulation (with something like ByteBuddy).

    Example that does not work:

    class MyClass {
        public String doSomething() {
            return doSomethingElse();
        }
        
        public String doSomethingElse() {
            return "foo";
        }
    }
    
    // ...
    
    class MyTest {
    
        private MyClass myClass;
    
        // ...
        @Test
        void myTest() {
            when(myClass.doSomethingElse()).thenReturn("Bar");
            assertEquals("bar", myClass.doSomething()); // FAILS
        }
    }
    

    Example that does work, using an intermediate class with overrideable methods:

    class MyClass {
    
        private MyClassBackend myClassBackend;
    
        public MyClass(MyClassBackend myClassBackend) {
            this.myClassBackend = myClassBackend;
        }
    
        public String doSomething() {
            return myClassBackend.doSomethingElse();
        }
    }
    
    class MyClassBackend {
        public String doSomethingElse() {
            return "foo";
        }
    }
    
    // ...
    
    class MyTest {
    
        private MyClass myClass;
    
        @Mock
        private MyClassBackend myClassBackend;
    
        @BeforeEach
        void setUp() {
            this.myClass = new MyClass(myClassBackend);
        }
    
        // ...
        @Test
        void myTest() {
            when(myClassBackend.doSomethingElse()).thenReturn("bar");
            assertEquals("bar", myClass.doSomething()); // PASSES
        }
    }