TooManyActualInvocations exception when creating #Nested tests in #WebMvcTest - spring

I am trying to group my tests in a #WebMvcTest using #Nested, but unfortunately, a few of my tests stated failing with TooManyActualInvocations exception.
Here is minimal example I arrived at:
The test:
#WebMvcTest(value = AController.class)
public class AControllerMvcTest {
#MockBean
BService bService;
#Autowired
MockMvc mockMvc;
#Nested
class NestedTests1 {
#Test
void testOne() throws Exception {
System.out.println("Test One started");
Mockito.when(bService.getDummyString()).thenReturn("dummyResp1");
mockMvc.perform(MockMvcRequestBuilders.post("/underTest"))
.andExpect(MockMvcResultMatchers.status().isOk());
Mockito.verify(bService).getDummyString();
System.out.println("Test One finished");
}
}
#Nested
class NestedTests2 {
#Test
void testTwo() throws Exception {
System.out.println("Test Two started");
System.out.println(bService.hashCode());
Mockito.when(bService.getDummyString()).thenReturn("dummyResp2");
mockMvc.perform(MockMvcRequestBuilders.post("/underTest"))
.andExpect(MockMvcResultMatchers.status().isOk());
Mockito.verify(bService).getDummyString();
System.out.println("Test Two finished");
}
}
}
AController:
#Controller
class AController {
final BService bService;
#Autowired
AController(BService bService) {
this.bService = bService;
}
#PostMapping("/underTest")
String methodUnderTest() {
return bService.getDummyString();
}
}
BService:
#Service
class BService {
String getDummyString() {
return "ABC";
}
}
The exception I receive:
org.mockito.exceptions.verification.TooManyActualInvocations:
mypackage.controller.BService#0 bean.getDummyString();
Wanted 1 time:
-> at mypackage.controller.AControllerMvcTest$NestedTests1.testOne(AControllerMvcTest.java:45)
But was 2 times:
-> at mypackage.controller.AController.methodUnderTest(AController.java:19)
-> at mypackage.controller.AController.methodUnderTest(AController.java:19)
The same test passes if there are no nested tests.
In both cases (nested tests and no nested tests)
the tests are executed sequentially (println to the console)
bService is the same instance in both tests (checked with debugger)
My Questions
why adding #Nested caused my test to fail
how to fix it?

The Spring testing support does not work with nested tests unless you repeat all spring-related annotations in all nested test classes. I recommend to forgo nesting in this case.

It is an open bug in Spring Boot: #MockBean fields are not reset for JUnit 5 #Nested tests #12470.
It is blocked by another issue in Spring Framework: Discover test configuration on enclosing class for nested test class [SPR-15366] #19930
The latter is scheduled for 5.3 M2, so hopefully this won't be an issue soon.

Related

Mockito Null Pointer Exception when initializing using #RunWith annotation when mocking

I'm using Mockito to create mock beans, and for some reason, when I initialize a mock with #RunWith(MockitoJUnitRunner.class), I get an error when I call a method from the mocked class. However, if I initialize using #BeforeEach, it works fine. to be more specific:
#RunWith(MockitoJUnitRunner.class)
class Test {
#Mock
Bean mockBean;
#Test
void testGet() {
Mockito.when(mockBean.method()).thenReturn(2);
assertEquals(2, mockBean.method());
}
The above causes a NullPointException error on the line Mockito.when(mockBean.method()).thenReturn(2);
H**owever, if I do the below:
class Test {
#Mock
Bean mockBean;
#BeforeEach
public void init() {
MockitoAnnotations.initMocks(this);
}
#Test
void testGet() {
Mockito.when(mockBean.method()).thenReturn(2);
assertEquals(2, mockBean.method());
}
the test runs just fine. I was under the impression that the two should work the same. I'd appreciate it if anyone could tell me what I am doing wrong or misunderstanding.

#SpyBean with few Integration tests doesn't work correctly

I have a strange behavior of #SpyBean field in my integration tests.
For example, I have a few integration tests:
package a;
#SpringBootTest
public class A {
#SpyBean
public MySpyBeanCandidate spyBean;
#Test
public void test1 {
// some work
Mockito.verify(spyBean, Mockito.atLeastOnce()).methodName(eq("String value"), anyString());
}
}
package a;
#SpringBootTest
public class B {
#SpyBean
public MySpyBeanCandidate spyBean;
#Test
public void test2 {
// some work
Mockito.verify(spyBean, Mockito.atLeastOnce()).methodName(eq("String value"), anyString());
}
}
The problem is when I try to execute them separately they executed successfully, but if I'll run them together, in the second test Mockito.verify(..) will throw an exception: Wanted but not invoked. But I have debugged it and checked that method (methodName) called correctly. Who knows why this is happening?
#DirtiesContext on each #Test method for such cases worked for me.

SpringBootTest with MockBean is not returning what I expect

Versions:
Java: 1.8
Spring Boot: 1.5.4.RELEASE
Application Main:
#SpringBootApplication
public class SpringbootMockitoApplication implements CommandLineRunner {
#Autowired
MyCoolService myCoolService;
public static void main(String[] args) {
SpringApplication.run(SpringbootMockitoApplication.class, args);
}
#Override
public void run(String... strings) throws Exception {
System.out.println(myCoolService.talkToMe());
}
}
My Service Interface:
public interface MyCoolService {
public String talkToMe();
}
My Service Implementation:
#Service
public class MyCoolServiceImpl implements MyCoolService {
#Override
public String talkToMe() {
return "Epic Win";
}
}
My Test class:
#RunWith(SpringRunner.class)
#SpringBootTest
public class SpringbootMockitoApplicationTests {
#MockBean
private MyCoolService myCoolService;
#Test
public void test() {
when(myCoolService.talkToMe()).thenReturn("I am greater than epic");
}
}
Expected Output: I am greater than epic
Actual Output: null
I simply want to replace the bean instance in the context with a mock that will return "I am greater than epic". Have I misconfigured something here?
The run method of any CommandLineRunners is called as part of SpringApplication being run. This happens when the test framework is bootstrapping the application context for your tests. Crucially, this is before your test method has set any expectations on your MyCoolService mock. As a result the mock returns null when talkToMe() is called.
Something may have been lost in reducing your problem to a simple example, but I don't think I'd use an integration test here. Instead, I'd unit test your CommandLineRunner with a mock service. To so so, I'd recommend moving to constructor injection so that you can pass the mock directly into the service's constructor.

Spring boot test - PowerMockito to mock and stub constructor

Using Spring boot starter test for testing my application but I am using third party library. Lets suppose we have a class TRequest and it has some constructor and I want to mock and stub that constructor to return the result.
#SpringBootTest
#RunWith(SpringRunner.class)
#PrepareForEverythingForTest
public class TestClass {
#MockBean
TRequest trequest ;
#Before
public void setUp() throws Exception {
PowerMockito.whenNew(TRequest.class).withAnyArguments().thenReturn(trequest);
}
}
Now when I am trying to create the constructor using new, it is not returning the correct stubbed result.
TRequest trequest1 = new TRequest("apiKey","secretKey") ;
trequest.equals(trequest1) ; // false but I want it to be true
Have used a jackson third party lib to test with. - getting ClassLoader exceptions because of PowerMock though.
#SpringBootTest
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(SpringRunner.class)
public class TestPowerMockito {
#MockBean
ObjectMapper object;
#Before
public void init() throws Exception {
PowerMockito.whenNew(ObjectMapper.class).withAnyArguments().thenReturn(object);
}
#Test
public void test() {
assertEquals(object, new ObjectMapper());
}
}

Resetting Mockito mocks, provided as Spring beans, between tests?

I have a Java application that uses Spring's dependency injection. I want to mock out a bean, and verify that it receives certain method calls.
The problem is that Mockito does not reset the mock between tests, so I cannot correctly verify method calls on it.
My unit under test:
public class MyClass {
#Resource
SomeClientClass client;
public void myMethod() {
client.someMethod();
}
}
The unit test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = UnitTestConfig.class)
public class MyClassTest {
#Resource
SomeClientClass client;
#Test
public void verifySomething() {
// ...
Mockito.verify(client).publish();
}
}
Finally,
#Configuration
public class UnitTestConfig {
#Bean
SomeClientClass client() {
return Mockito.mock(SomeClientClass.class);
}
}
Though I could hack my way around this problem by manually resetting mocks between tests, I wonder if there's a cleaner / more idiomatic approach.
I had to add this at the start:
#BeforeEach
void setup() {
Mockito.reset(...mockBeans);
...
}
Author not explained why he needs it, I can put more details.
Combining Spring's dependency injection with Mockito in this way not the best approach.
It leads to errors, because same Mocks will be reused between different tests!
This means that verify() will work incorrectly. It will accumulate method invocations from different tests. For example you will get "Wanted 1 time:" - "But was 2 times".
Most generic solution for this in Mockito is using #InjectMocks.
This annotation doing 2 important things:
actually injecting all #Mock fields into class annotated with #InjectMocks
resets each #Mock annotated class. (so, verify() will not accumulate invocations from different tests)
Code example:
#RunWith(MockitoJUnitRunner.class)
public class SomeSpringConverterTest {
#InjectMocks
private SomethingToJsonNodeSpringConverter someSpringConverter;
#Mock
private SomethingDatamodelService someDatamodelService;
#Test
public void convertOriginalContainerTest() {
SomethingContainer someContainer = buildSomeContainer("aa", "bb");
Mockito.when(someDatamodelService.getAttributes()).thenReturn(Arrays.asList("aa", "bb"));
JsonNode node = someSpringConverter.convert(someContainer, JsonNode.class);
Mockito.verify(someDatamodelService.getAttributes());
assertTrue(node.get("aa") != null);
}
#Test
public void convertOriginalContainerTest() {
SomethingContainer someContainer = buildSomeContainer("aa", "bb");
Mockito.when(someDatamodelService.getAttributes()).thenReturn(Arrays.asList("aa", "bb"));
JsonNode node = someSpringConverter.convert(someContainer, JsonNode.class);
Mockito.verify(someDatamodelService.getAttributes());
assertTrue(node.get("bb") != null);
}
}

Resources