How to mock a private dao variable?

2019-04-21 20:34发布

问题:

I have a dao.create() call that I want to mock when testing a method. But I am missing something as I'm still getting NPE. What is wrong here?

class MyService {
    @Inject
    private Dao dao;

    public void myMethod() {
        //..
        dao.create(object);
        //
    }
}

How can I mock out the dao.create() call?

@RunWith(PowerMockRunner.class)
@PrepareForTest(DAO.class)
public void MyServiceTest {

    @Test
    public void testMyMethod() {
        PowerMockito.mock(DAO.class);

        MyService service = new MyService();
        service.myMethod(); //NPE for dao.create()
    }
}

回答1:

You are not injecting the DAO. With mockito you can change your test class to use @InjectMocks and use mockito runner.

@RunWith(MockitoJUnitRunner.class)
public void MyServiceTest {
    @Mock
    private Dao dao;
    @InjectMocks
    private MyService myService;
    ...
}

You can read more about InjectMocks at Inject Mocks API

Simpler way is changing your injection to injection by constructor. For example, you would change MyService to

class MyService {
    ...
    private final Dao dao;

    @Inject
    public MyService(Dao dao) {
        this.dao = dao;
    } 
    ...
}

then your test you could simple pass the mocked DAO in setup.

...
@Mock
private Dao dao;

@Before
public void setUp() {
    this.dao = mock(Dao.class);
    this.service = new MyService(dao);
}
...

now you can use verify to check if create was called, like:

...
   verify(dao).create(argThat(isExpectedObjectBeingCreated(object)));
}

private Matcher<?> isExpectedObjectBeingCreated(Object object) { ... }

Using injection by constructor will let your dependencies clearer to other developers and it will help when creating tests :)



回答2:

You still need to set the dao field with your mock. You can use reflection to this.



回答3:

You need to inject/set the mocked object DAO in your service class.

If it is a spring based project, you may have a look @ Spring Junit Testrunner



回答4:

If you use new MyService() the Dao is never injected. For the Dao to be injected you need to load the MyService via an ApplicationContext (Spring) or an Injector (Guice). Like you would in your normal application.



回答5:

As others have already said, you need to set the dao field in your MyService class in some fashion. I'm unsure the mechanism to allow for a compound runner on your test to use both Powermock and a DI framework runner (assuming Powermock is required), but as long as you're already using PowerMock (for reasons unclear in the given example), you could avail yourself of the Whitebox class to set the dao more manually.

public void testMyMethod() {
    Dao dao = mock(Dao.class)
    doNothing().when(dao).create(anyObject())); //assuming no return val for dao.create()

    MyService service = new MyService();
    Whitebox.setInternalState(service, "dao", dao);

    service.myMethod(); 
}