1

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!

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Don't mock the methods of the class being tested - mock the dependencies those methods use. In this case, there is probably a `repository` (or similar) that those methods delegate to - so mock the `repository`. If there is no `repository`, then create it! Fix the code so it's easy to test (rather than fight how to test). – Andrew S Jan 29 '21 at 17:41
  • You need to use "@Mock" and "@InjectMocks". – sonny Jan 29 '21 at 18:02
  • 1
    @sonny I have used both, then also same issue. Please check the question again I have added the code –  Jan 29 '21 at 20:02

1 Answers1

0

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
    }
}
Paul Benn
  • 1,911
  • 11
  • 26
  • 1
    If I put the functions which I want to mock in a new class then I should create object of the new class ? then how to hit from the actual service? Can you show me a similar working example ? –  Jan 29 '21 at 16:46
  • I've edited my answer to reflect a working set-up – Paul Benn Jan 29 '21 at 16:53