#SpyBean with few Integration tests doesn't work correctly - spring

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.

Related

Make #SpringBootTest use a new application on every test

Is there any way to make Spring boot use a completely fresh ApplicationContext on every single #Test method execution and discard the previous application context ?
Anyway to change the default behavior of reusing ApplicationContext ?
You can annotate a test method with #DirtiesContext to indicate the ApplicationContext after running this test method is dirty such that when it executes the next test method , it will completely refresh the ApplicationContext :
#SpringBootTest
public class FooTest {
#Test
#DirtiesContext
public void test1() {
}
#Test
#DirtiesContext
public void test2() {
}
}

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.

TooManyActualInvocations exception when creating #Nested tests in #WebMvcTest

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.

MockBean stubbing ineffective

I have a configuration class with a few MockBeans replacing actual beans in context for tests.
#Configuration
public class MyTestConfig {
#MockBean
private MyService myService;
}
I use those mocks in my tests:
#Import({ MyTestConfig .class })
public class MyTest {
#Autowired
private MyService myService;
#Test
public void aTest() {
...
}
}
First the idea was to add the stubbing in this MyTestConfig configuration class, so that the mock is pre-made for all tests, so I did it in a #PostConstruct method, and it worked just fine - the mock in test did return the expected value:
#PostConstruct
public void init() {
when(myService.foo("say hello")).thenReturn("Hello world");
}
It turned out though, that constructing a pre-made mock suitable for all test can be tricky, so we decided to move the stubbing to tests.
#Test
public void aTest() {
when(myService.foo("say hello")).thenReturn("Hello world");
}
And this doesn't work - the stubbed method returns null. We want to leave MockBeans in the configuration class, but stub them in tests, so any advice on why the stubbing is ineffective?
Spring Boot 2.0.5, Mockito 2.22.0
Yes, stubbing should be performed inside their respective test cases (unless you have a test class that shares the stubbing scenarios but it all comes down to preference).
However, for creating #MockBeans, you would need to use a #SpringBootTest in order to get the actual beans replaced with mocks. This could be done as simply as this example:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTest {
#Autowired
private MyTestClass testClass;
#MockBean
private MyService service;
#Test
public void myTest() {
// testing....
}
}

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.

Resources