ChatGPT解决这个技术问题 Extra ChatGPT

Mockito, JUnit and Spring

I started to learn about Mockito only today. I wrote some simple test (with JUnit, see below), but I can't figure out how can I use mock object inside Spring's managed beans. What is best practices for working with Spring. How should I inject mocked dependency to my bean?

You can skip this till back to my question.

First of all, what I've learned. This is very good article Mocks Aren't Stubs that explains the basics (Mock's checks behavior verification not state verification). Then there is a good example here Mockito and here Easier mocking with mockito. We have explanation that Mockito's mock objects are both mock and stub.

Here Mockito and here Matchers, you can find more examples.

This test

@Test
public void testReal(){
    List<String> mockedList = mock(List.class);
     //stubbing
     //when(mockedList.get(0)).thenReturn("first");

    mockedList.get(anyInt());
    OngoingStubbing<String> stub= when(null);
    stub.thenReturn("first");

    //String res = mockedList.get(0);
                //System.out.println(res);

     //you can also verify using argument matcher
     //verify(mockedList).get(anyInt());

    verify(mockedList);
    mockedList.get(anyInt());
}

works just fine.

Back to my question. Here Injecting Mockito mocks into a Spring bean somebody tries to use Springs ReflectionTestUtils.setField(), but then here Spring Integration Tests, Creating Mock Objects we have recommendation to change Spring's context.

I didn't really understand last two links... Can somebody explain to me what problem does Spring have with Mockito? What's wrong with this solution?

@InjectMocks
private MyTestObject testObject

@Mock
private MyDependentObject mockedObject

@Before
public void setup() {
        MockitoAnnotations.initMocks(this);
}

https://stackoverflow.com/a/8742745/1137529

EDIT: I wasn't really clear. I will provide 3 examples of code to clarify my self: Suppose, we have bean HelloWorld with method printHello() and bean HelloFacade with method sayHello that forward calls to HelloWorld's method printHello().

First example is using Spring's context and without custom runner, using ReflectionTestUtils for dependency injection (DI):

public class Hello1Test  {
private ApplicationContext ctx;

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
    this.ctx = new ClassPathXmlApplicationContext("META-INF/spring/ServicesImplContext.xml");
}



@Test
public void testHelloFacade() {
    HelloFacade obj = (HelloFacade) ctx.getBean(HelloFacadeImpl.class);
    HelloWorld mock = mock(HelloWorld.class);
    doNothing().when(mock).printHello();

    ReflectionTestUtils.setField(obj, "hello", mock);
    obj.sayHello();

    verify(mock, times(1)).printHello();
}

}

As @Noam pointed out thereis way to run it wihtout explicit call to MockitoAnnotations.initMocks(this);. I will also drop using of the Spring's context on this example.

@RunWith(MockitoJUnitRunner.class)
public class Hello1aTest {


@InjectMocks
private HelloFacade obj =  new HelloFacadeImpl();

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Another way to do this

public class Hello1aTest {

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
}


@InjectMocks
private HelloFacadeImpl obj;

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Noth, that in preivious example we have to manually instaniate HelloFacadeImpl and assign it to HelloFacade, beacuse HelloFacade is interface. In the last example, we can just declare HelloFacadeImpl and Mokito will instantiate it for us. The drawback of this approach that now, unit-under-test is impl-class and not interface.

Is there something wrong with the solution? The blog post you link to isn't using @InjectMocks (relatively recent, although IIRC before that blog post) so there are circumstances where re-ordering the bean definitions might be necessary. I'm not sure what the question is, ultimately.
Spring has no problem with Mockito. Or vice versa.
I believe in most case, you SHOULD test against the actual implementation instead of the interface.
I've opened new question about this issue stackoverflow.com/questions/10937763/…

C
Cleankod

Honestly I am not sure if I really understand your question :P I will try to clarify as much as I can, from what I get from your original question:

First, in most case, you should NOT have any concern on Spring. You rarely need to have spring involved in writing your unit test. In normal case, you only need to instantiate the system under test (SUT, the target to be tested) in your unit test, and inject dependencies of SUT in the test too. The dependencies are usually a mock/stub.

Your original suggested way, and example 2, 3 is precisely doing what I am describing above.

In some rare case (like, integration tests, or some special unit tests), you need to create a Spring app context, and get your SUT from the app context. In such case, I believe you can:

1) Create your SUT in spring app ctx, get reference to it, and inject mocks to it

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Before
    /* Initialized mocks */
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void someTest() {
         // ....
    }
}

or

2) follow the way described in your link Spring Integration Tests, Creating Mock Objects. This approach is to create mocks in Spring's app context, and you can get the mock object from the app ctx to do your stubbing/verification:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    TestTarget sut;

    @Autowired
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}

Both ways should work. The main difference is the former case will have the dependencies injected after going through spring's lifecycle etc. (e.g. bean initialization), while the latter case is injected beforehands. For example, if your SUT implements spring's InitializingBean, and the initialization routine involves the dependencies, you will see the difference between these two approach. I believe there is no right or wrong for these 2 approaches, as long as you know what you are doing.

Just a supplement, @Mock, @Inject, MocktoJunitRunner etc are all unnecessary in using Mockito. They are just utilities to save you typing the Mockito.mock(Foo.class) and bunch of setter invocations.


@Noam, take a look on this asnwer. Adrian, great answer thank you. :-)
I don't think 1) works. That is: when using both @Autowired and @InjectMocks, I see the Spring-injected beans, not the mocks, in TestTarget. (I was hoping that using both annotations would only inject a mock for Foo, but still use the default Spring-injected beans for all the other dependencies that are auto-wired in TestTarget but are not mocked in the integration test. No cigar, it seems. Spring 3.1.2; Mockito 1.9.5)
Though I haven't tried that, I believe it should work. @Autowired is handled by the Spring JUnit Runner, which is handled before setup(). @InjectMocks is handled by MockitoAnnotaitons.initMocks(this) in setup(). I don't see any reason to stop it working. I may have a try on it to confirm if it work. :)
I did include the MockitoAnnotations.initMocks(this) in the @Before. Also, removing @Autowired (hence only leaving @InjectMocks in place) does give me the mock in TestTarget. (But removing @Autowired also leaves all other beans uninitialized by Spring.) However, some more investigation shows that I need a TestTarget#setFoo(Foo f) method. Without it, @InjectMocks works just fine, unless combined with @Autowired. So: when using both @Autowired and @InjectMocks, then @Autowired private Foo mockFoo; does not suffice. Might need a bug report; will investigate.
Unfortunately it does matter if one uses @InjectMocks TestTarget sut or @Autowired @InjectMocks TestTarget sut. For the former, Spring does not inject anything into TestTarget (as expected) and it suffices if TestTarget defines private properties to have Mockito inject its beans. For the latter, TestTarget needs a public setter for each bean Mockito is supposed to inject. Using the latter for a target that does not define a public setter for Foo just gets me the Spring injected beans in TestTarget, where the Spring-injected Foo is not replaced by the Mockito mock.
D
Dave Newton

Your question seems to be asking about which of the three examples you have given is the preferred approach.

Example 1 using the Reflection TestUtils is not a good approach for Unit testing. You really don't want to be loading the spring context at all for a unit test. Just mock and inject what is required as shown by your other examples.

You do want to load the spring context if you want to do some Integration testing, however I would prefer using @RunWith(SpringJUnit4ClassRunner.class) to perform the loading of the context along with @Autowired if you need access to its' beans explicitly.

Example 2 is a valid approach and the use of @RunWith(MockitoJUnitRunner.class) will remove the need to specify a @Before method and an explicit call to MockitoAnnotations.initMocks(this);

Example 3 is another valid approach that doesn't use @RunWith(...). You haven't instantiated your class under test HelloFacadeImpl explicitly, but you could have done the same with Example 2.

My suggestion is to use Example 2 for your unit testing as it reduces the code clutter. You can fall back to the more verbose configuration if and when you're forced to do so.


g
geoand

The introduction of some new testing facilities in Spring 4.2.RC1 lets one write Spring integration tests that don't rely on the SpringJUnit4ClassRunner. Check out this part of the documentation.

In your case you could write your Spring integration test and still use mocks like this:

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}

Here youtube.com/watch?v=-_aWK8T_YMI one can see video about Spring Framework on Java 8.
N
Noam

You don't really need the MockitoAnnotations.initMocks(this); if you're using mockito 1.9 ( or newer ) - all you need is this:

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

The @InjectMocks annotation will inject all your mocks to the MyTestObject object.


It's the use of @RunWith(MockitoJUnitRunner.class) that removes the need to call MockitoAnnotations.initMocks(this) explicitly. As described here the annotation has been available since 1.8.3
@Brad Running with the Mockito runner isn't always possible, though, that's all.
A
Adriaan Koster

Here's my short summary.

If you want to write a unit test, don't use a Spring applicationContext because you don't want any real dependencies injected in the class you are unit testing. Instead use mocks, either with the @RunWith(MockitoJUnitRunner.class) annotation on top of the class, or with MockitoAnnotations.initMocks(this) in the @Before method.

If you want to write an integration test, use:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("yourTestApplicationContext.xml")

To set up your application context with an in-memory database for example. Normally you don't use mocks in integration tests, but you could do it by using the MockitoAnnotations.initMocks(this) approach described above.


j
jhericks

The difference in whether you have to instantiate your @InjectMocks annotated field is in the version of Mockito, not in whether you use the MockitoJunitRunner or MockitoAnnotations.initMocks. In 1.9, which will also handle some constructor injection of your @Mock fields, it will do the instantiation for you. In earlier versions, you have to instantiate it yourself.

This is how I do unit testing of my Spring beans. There is no problem. People run into confusion when they want to use Spring configuration files to actually do the injection of the mocks, which is crossing up the point of unit tests and integration tests.

And of course the unit under test is an Impl. You need to test a real concrete thing, right? Even if you declared it as an interface you would have to instantiate the real thing to test it. Now, you could get into spies, which are stub/mock wrappers around real objects, but that should be for corner cases.


Well, I really want to test my "contract" which is defined in the interface. If my Impl happen to have some public method that is not exposed through the interface, for me it is like private method and I tend to ignore it.
But what fulfills the "contract"? The Impl, right? So that's what you test. What would it even look like to test just the interface? You can't instantiate an interface.
I've opened new question about this issue stackoverflow.com/questions/10937763/…
OK, it's clear from your other question that we were just confusing each other with vocabulary or something. Of course the instance you are testing is the Impl, but do you declare it as the interface or the Impl? I think that is context specific, but it's certainly less nonsensical than what I had thought you were asking originally.
l
luboskrnac

If you would migrate your project to Spring Boot 1.4, you could use new annotation @MockBean for faking MyDependentObject. With that feature you could remove Mockito's @Mock and @InjectMocks annotations from your test.