Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Mockito when we cannot pass a mock object to an instance of a class

Tags:

java

mockito

Suppose I have a class like that:

public class MyClass {

    Dao dao;

    public String myMethod(Dao d) {

        dao = d;

        String result = dao.query();

        return result;
    } 
}

I want to test it with mockito. So I create a mock object and I call the method to test in that way:

Dao mock = Mockito.mock(Dao.class);

Mockito.when(mock.myMethod()).thenReturn("ok");

new MyClass().myMethod(mock);

But, suppose instead I have a class like that:

public class MyClass {

    Dao dao = new Dao();

    public String myMethod() {

        String result = dao.query();

        return result;
    } 
}

Now I cannot pass my mock as an argument, so how I gonna test my method? Can someone show an example?

like image 780
user1883212 Avatar asked Dec 18 '14 17:12

user1883212


Video Answer


2 Answers

Fundamentally, you're trying to replace a private field with an alternative implementation, which means you'd violate encapsulation. Your only other option is to restructure the class or method, to make it better-designed for testing.

There are a lot of short answers in the comments, so I'm aggregating them here (and adding a couple of my own) as Community Wiki. If you have any alternatives, please feel free to add them here.

Restructure the class

  • Create a setter for the field in question, or relax the field's visibility.

  • Create a dependency-injecting override or static method that takes a DAO, and make the public instance method delegate to it. Test the more-flexible method instead.

    public String myMethod() { return myMethod(dao); }
    String myMethod(Dao dao) { /* real implementation here */ }
    
  • Add a constructor overload or static factory method that replaces private fields for the sake of testing.

  • Fully structure the class for dependency injection. (Sotirios Delimanolis, EJK)

Note that some of these can be package-private for testing, if you put your tests in the same Java package (possibly in a separate source tree). In all cases, good names and documentation are helpful to make your intentions clear.

Violate encapsulation

  • Use reflection to set private fields in the class. (kinbiko - see answer)
  • Use PowerMockito to replace the Dao constructor with a mock of your choice. (Dave Newton)
like image 161
4 revs Avatar answered Sep 30 '22 05:09

4 revs


how about this?

public class MyClassTest {

    MyClass myClass = new MyClass();
    Dao dao = Mockito.mock(Dao.class);

    public void testMyMethod() {

        Field field = myClass.getClass().getDeclaredField("dao");
        field.setAccessible(true);
        field.set(myClass, dao);
        //Do the test...
    } 
}

EDIT: As mentioned in the comments this assumes that you don't change the name of the dao field. It might be a good idea then to get all the fields, Field[] fields = myClass.getClass().getDeclaredFields(); and iterate over them, getting the field(s) of type Dao. Then proceeding as above. This way your test doesn't depend on the name of your field anymore.

like image 40
kinbiko Avatar answered Sep 30 '22 06:09

kinbiko