Difference between Import/Autowire/MockBean/ContextConfiguration in Spring test - java

I frequently see Spring test like this:
#WebMvcTest(controllers = MyController.class)
#Import({DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, JpaConfig.class, MyService.class})
#ContextConfiguration(classes = AppConfig.class)
#MockBeans({#MockBean(MyLocationProvider.class), #MockBean(MyOtherService.class)})
public class MyServiceControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private MyJobRepository myJobRepository;
#MockBean
private MyComplicatedService myComplicatedService;
....
}
I can see there are #Import, #ContextConfiguration, #MockBeans(Before the class), #MockBeans(As fields), #Autowired.
I can understand the different between #MockBeans(Before the class), #MockBeans(As fields), #Autowired. But I am not sure what is the difference between #Import and #ContextConfiguration.
Can someone help?

Related

can we use #Autowired without bean registration?

In the code below, surprisingly, when I use #AutoWired, the fields are set but this class has not been registered as a bean, and the program works correctly, but when I took the MockingTest bean from the context, it said that there is no such bean. Is it possible to use AutoWired without registering a class as a bean?
#SpringBootTest
public class MockingTest {
#Autowired
private ApplicationContext context;
#Autowired
private CollegeStudent collegeStudent;
#Autowired
private StudentGrades studentGrades;
#Mock
private ApplicationDao applicationDao;
#InjectMocks
private ApplicationService applicationService;
#BeforeEach
void setUp() {
collegeStudent.setFirstname("Al");
collegeStudent.setLastname("Zam");
collegeStudent.setEmailAddress("555#gmail.com");
collegeStudent.setStudentGrades(studentGrades);
}
#Test
#DisplayName("mockito testing")
public void testMocking() {
when(applicationDao.addGradeResultsForSingleClass(studentGrades.getMathGradeResults()))
.thenReturn(100.0);
assertEquals(100.0, applicationService.addGradeResultsForSingleClass(studentGrades.getMathGradeResults()));
verify(applicationDao).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
verify(applicationDao,times(1)).addGradeResultsForSingleClass(studentGrades.getMathGradeResults());
}
}
#SpringBootTest sets up multiple hooks into the test runner. One of them is to inject beans into #Autowired fields, without the test class itself being a bean.
#SpringBootTest actually does a lot of "magic" behind the scenes, just so that things just work like we might expect, without us thinking about them too much.

Different annotations for unit tests in Spring app

I am writing some unit tests and I am confused due to the different annotations in unit tests:
One of them is like:
#ExtendWith(SpringExtension.class)
class EmployeeServiceImplTest {
#MockBean
private EmployeeRepository employeeRepository;
#Autowired
private EmployeeServiceImpl employeeService;
#Test
void testFindAll() {
//...
}
//...
}
And another is using the following annotations:
#RunWith(MockitoJUnitRunner.class)
public class EmployeeServiceImplTest {
#Mock
private EmployeeRepository employeeRepository;
#InjectMocks
private EmployeeServiceImpl employeeService;
#Test
void testFindAll() {
//...
}
//...
}
So, I use Java and Spring Boot for language and framework. So, which annotations should I use?
#ExtendWith(SpringExtension.class) or #RunWith(MockitoJUnitRunner.class),
#MockBean or #Mock,
#Autowired or #InjectMocks
Any idea regarding to these annotations?

Problem autowiring UserDetailsService while running tests

I added spring security to my application and it's working great, but now existing tests are broken, when I run them I get:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field userDetailsService in xxx.xxx.someapp.spring.security.WebSecurityConfig required a bean of type 'xxx.xxx.sardinatank.someapp.security.services.UserDetailsServiceImpl' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
This is how I'm wiring the UserDetailsServiceImpl in my WebSecurityConfig
#Autowired
UserDetailsServiceImpl userDetailsService;
This is an example controller
#RestController
public class HelloWorldController {
#GetMapping
public ModelAndView helloWorld() {
final ModelAndView bootstrapFront = new ModelAndView("index.html");
return bootstrapFront;
}
}
And this is a test that fails
#WebMvcTest(controllers = HelloWorldController.class)
#ContextConfiguration
#WebAppConfiguration
class HelloWorldControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#BeforeEach
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void getBootstrappedDoc() throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andReturn();
assertEquals("index.html", mvcResult.getModelAndView().getViewName());
}
}
I guess you might have declared it as a #Component or something that is not part of this list:
#Controller, #ControllerAdvice, #JsonComponent, Converter, Filter, WebMvcConfigurer
which means UserdetailServiceImpl will not be part of the #SpringWebMVC sliced test context, hence spring doesn't bootstrap it.
You have 2 options:
Either you decorate your UserServiceImpl with one of the aforementioned annotations: e.g
#JsonComponent
class UserDetailsServiceImpl implements userDetailsService { ... };
Or you inject a mock of UserServiceImpl in your test with #MockBean:
#WebMvcTest(controllers = HelloWorldController.class)
#ContextConfiguration
#WebAppConfiguration
class HelloWorldControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
UserDetailsServiceImpl userDetailsService;
private MockMvc mockMvc;
//...
}

Spring testing annotations

I've been working on a personal project very recently and looking at my test file I realized I have some regarding spring annotations:
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class BookingServicesTests {
private MockMvc mvc;
#Mock
private BookingRepository bookingRepository;
#InjectMocks
private BookingResource bookingController;
#Before
public void setup() {
JacksonTester.initFields(this, new ObjectMapper());
mvc = MockMvcBuilders
.standaloneSetup(bookingController)
.setControllerAdvice(new ConflictExceptionController())
.build();
}
...
}
So the thing is that #SpringBootTest is made to test your application using real HTTP methods. But in my setup method I included a MockMvcBuilders statement, which is a standalone test (no server and no application context).
My question is:
Are those elements incompatible?
One element obfuscate the other? This is: by using MockMvcBuilder can I get rid of #SpringBootTest?
Thanks
Use one or the other, not both. You are only allowed one JUnit's #runwith() and the value you pass in, whether it be SpringRunner.class or MockitoJUnitRunner.class, has very different behaviors.
So the code you posted is incorrect as #SpringBootTest will try to load the application context when your test class is "running with MockitoJUnitRunner". Therefore #SpringBootTest should be used along with #runWith(SpringRunner.class), as such
#RunWith(SpringRunner.class)
#WebMvcTest(BookingResource.class) // multiple controller class could go here
#AutoConfigureMockMvc
public class BookingServicesTests {
#Autowired
private MockMvc mvc;
#MockBean
private BookingRepository bookingRepository;
...
}
Notice how I replace #SpringBootTest() with #WebMvcTest(). This is because #WebMvcTest() only scans components that are #Controller and loads configuration for the web layer, whereas #SpringBootTest() does so for the entire application.
Or what you did with Mockito without Spring:
#RunWith(MockitoJUnitRunner.class)
public class BookingServicesTests {
private MockMvc mvc;
#Mock
private BookingRepository bookingRepository;
#InjectMocks
private BookingResource bookingController;
...
}

Why is the constructor method being called before setup

Here is my class under test:
KafkaProcessorApplication
#EnableBinding(Processor.class)
#EnableConfigurationProperties(KafkaProperties.class)
public class KafkaProcessorApplication {
#Autowired
private Processor processor;
#Autowired
private KafkaProperties kafkaProperties;
private KafkaTemplate<String, String> kafkaTemplate;
#Autowired
KafkaProcessorApplication(SenderConfig senderConfig) {
this.kafkaTemplate = senderConfig.kafkaTemplate();
}
Here, SenderConfig is a just a simple config class with the method kafkaTemplate() creating a new instance of KafkaTemplate.
SenderConfig
#Configuration
public class SenderConfig {
#Autowired
KafkaProperties kafkaProperties;
public ProducerFactory<String, String> producerFactory() {
return new DefaultKafkaProducerFactory<>(new HashMap());
}
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(ProducerFactory()));
}
}
Here is the test class:
KafkaTestClass
#SpringBootTest
#ActiveProfiles("test")
#ContextConfiguration(classes = {SenderConfig.class, KafkaProcessorApplication.class})
#TestPropertySource(locations = "classpath:test-resources.properties")
#RunWith(SpringRunner.class)
public class KafkaProcessorApplicationTest {
#Autowired
private Processor processor;
#Mock
private SenderConfig senderConfig;
#Mock
private KafkaProperties kafkaProperties = new KafkaProperties();
#Mock private KafkaTemplate mockKafka;
#Autowired
#InjectMocks
private KafkaProcessorApplication app;
#Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
doReturn("ServerConfig").when(kafkaProperties).getServersConfig();
when(senderConfig.kafkaTemplate()).thenReturn(kafkaTemplate);
}
I want to mock kafkaTemplate. But, its instantiation is in constructor which is being executed even before the #Before is executed, where the logic of mocking the method is written.
Just curious why is the constructor being executed first, and also, how can I mock the method if this is the case?
What could be the approaches of mocking the kafkaTemplate, without using Powermock and without modifying the class under test as I can not change it?
When you use #SpringBootTest, the Spring dependency tree is resolved before the #Before method has a chance to execute. This includes constructing the KafkaProcessorApplication bean and its dependencies. This is why the constructor runs before #Before.
What you want is Spring's #MockBean to create and inject a mock bean in the application context.
This question has a great write-up of how you can use this: Difference between #Mock, #MockBean and Mockito.mock()
update
Now I see. The problem is that the KafkaProcessorApplication accesses the mock in its constructor before you can configure it.
This can be solved with a separate test Spring configuration that will return a configured SenderConfig mock bean as described here: Testing spring bean with post construct

Categories

Resources