9

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?

5

2 Answers 2

13

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

Sign up to request clarification or add additional context in comments.

Comments

2

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.

1 Comment

Not a bad approach, but has the obvious limitation that if the name of the field ever changes, the compiler will not catch this. This test would then fail at runtime.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.