Is it possible to invoke mocked object's metho

2019-07-31 01:42发布

问题:

I have, for example, this class:

public class A {
    private List<String> list;

    public A(B b){
        list = b.getList();
    }

    public List<String> someMethod(){
        return list;
    }
}

I want to unit test someMethod without invoking constructor. I use reflection to set list.

The problem is that I don't want to create B class object and I cannot mock it since it will cause NPE.

So my question is:

How to test someMethod without calling constructor of A? Is there any way to mock class A and doesn't lose posibility to call methods?

Creating constructor with zero arguments is not a solution.

Note: I don't want to change any part of A class. I'm asking if it is possible to perform this test without adding or changing anything in A class.

回答1:

You can test class A without calling it's constructor by Mockito. Not sure if I really understand your requirement but the following codes work for me.

import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.ArrayList;
import java.util.List;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ATest {

    @Test
    public void test() {
        A a = mock(A.class);
        when(a.someMethod()).thenCallRealMethod();
        List<String> listInA = new ArrayList<String>();
        ReflectionTestUtils.setField(a, "list", listInA);
        assertThat(a.someMethod(), is(listInA));
    }
}


回答2:

You should mock out the collaborators to your class -- that means that you can create an instance of the class being tested, and pass in mocks, configured to 'do the right thing' when it's methods are called.

In your example, you want to create a mock B, and use it like this:

@RunWith(MockitoJUnitRunner.class)
class myTest {
  @Mock private B b;

  public void someMethod() {
    doReturn(new ArrayList<String>()).when(b).getList();
    A a = new A(b);
    assertEquals("result", a.someMethod().get(0));
  }
}


回答3:

I don't want to create B class object

Add a constructor or factory method which doesn't require a B.

public A(B b){
    this(b.getList());
}

/* package local */ A(List<String> list){
    this.list = list;
}

By making the constructor package local it can be accessed by unit tests in the same package.

How to test someMethod without calling constructor of A?

You can use

A a = theUnsafe.allocateInstance(A.class);

but this is not recommended unless you have no other option e.g. deserialization.