Configuration Getting Ignored in Test - java

I try to test my spring app but encounter following problem:
In "normal mode"(mvn spring-boot:run) the app starts as expected and adapterConfig gets set and is NOT NULL. When I start my testclass to test the MVC, adapterConfig does not get set. Spring ignores the whole config class.
test:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = StudentController.class)
public class StudentControllerTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private StudentService service;
#MockBean
private StudentRepository repository;
#Test
public void shouldReturnABC() throws Exception{
MvcResult result = this.mockMvc.perform(get("/students/abc")).andReturn();
}
}
controller:
#RestController
#RequestMapping("/students")
#PermitAll
public class StudentController {
#Autowired
StudentService studentService;
//get
#GetMapping("/abc")
public String abc (){
return "abc";
}
config:
#Configuration
public class SpringBootKeycloakConfigResolver implements KeycloakConfigResolver {
private KeycloakDeployment keycloakDeployment;
private AdapterConfig adapterConfig;
#Autowired
public SpringBootKeycloakConfigResolver(AdapterConfig adapterConfig) {
this.adapterConfig = adapterConfig;
}
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
if (keycloakDeployment != null) {
return keycloakDeployment;
}
keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfig);
return keycloakDeployment;
}
}
adapterConfig is null when hitting the test but gets set & created when hitting it the normal way, any idea?

Using #WebMvcTest, the container will inject only components related to Spring MVC (#Controller, #ControllerAdvice, etc.) not the full configuration use #SpringBootTest with #AutoConfigureMockMvc instead.
Spring Boot Javadoc

Keycloak's AutoConfiguration is not included by #WebMvcTest.
You could
Include it manually via #Import(org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration.class)
Or use #SpringBootTest

with spring boot 2.5 i had I had to import KeycloakAutoConfiguration into my test.
#WebMvcTest(value = ApplicationController.class, properties = "spring.profiles.active:test")
#Import(KeycloakAutoConfiguration.class)
public class WebLayerTest {
// ... test code ....
}

Related

Why tested class based autowire annotation throw null exception?

I use Spring Boot 5 and JUnit in my project. I create a unit test to test the service.
Here is the service that I am testing:
#Service
#RequiredArgsConstructor
#Slf4j
public class BuilderServiceImpl implements BuilderService{
#Autowired
public AutoMapper autoMapper;
private final BuilderRepository builderRepository;
private final AdminUserRepository adminUserRepository;
#Override
public BuilderDto getByEmail(String email){
}
#Override
public List<BuilderMinDto> getAll() {}
#Override
public List<BuilderMinDto> getAll(int page, int size) {}
#Override
public SaveBuilderResponse create(Builder builder){
var str = autoMapper.getDummyText();
Builder savedBuilder = builderRepository.save(builder);
return new SaveBuilderResponse(savedBuilder);
}
}
And here is the test class that tests the service above:
#SpringBootTest
#RequiredArgsConstructor
#Slf4j
class BuilderServiceImplTest {
#Mock
private BuilderRepository builderRepository;
#Mock
private AdminUserRepository adminUserRepository;
private AutoCloseable autoCloseable;
private BuilderService underTest;
#BeforeEach
void setUp(){
autoCloseable = MockitoAnnotations.openMocks(this);
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
#AfterEach
void tearDown () throws Exception{
autoCloseable.close();
}
#Test
void getByEmail(){}
#Test
#Disabled
void getAll() { }
#Test
#Disabled
void testGetAll() {}
#Test
void create() {
//given
Builder builder = new Builder();
builder.setName("John Johnson");
builder.setCompanyName("Builders Test");
builder.setEmail("test#builders.com");
//when
underTest.create(builder);
//then
ArgumentCaptor<Builder> builderArgumentCaptor = ArgumentCaptor.forClass(Builder.class);
verify(builderRepository)
.save(builderArgumentCaptor.capture());
Builder captureBuilder = builderArgumentCaptor.getValue();
assertThat(captureBuilder).isEqualTo(builder);
}
}
When I start to run the test class the create method in BuilderServiceImpl fired and on this row:
var str = autoMapper.getDummyText();
I get NullPointerException(autoMapper instance is null).
Here is the definition of AutoMapper class:
#Component
#Slf4j
#RequiredArgsConstructor
public class AutoMapper {
public String getDummyText(){
return "Hello From AutoMapper.";
}
}
As you can see I use #Component annotation to register the AutoMapper class to the IoC container and Autowired annotation to inject it into autoMapper property in BuilderServiceImpl class.
Why autoMapper instance is null? How can I make autoMapper to be initialized?
In order to make #Autowire work you have to use the instance of BuilderServiceImpl (object under test) created by spring itself.
When you create the object like this (by yourself, manually):
#BeforeEach
void setUp(){
....
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
Spring doesn't know anything about this object, hence Autowiring won't work
Another thing that might be useful:
You've used #Mock for BuilderRepository and AdminUserRepository.
This are plain mockito annotation, and if you're using an integration/system test that runs the spring under the hood, probably this is not what you want:
Surely, it will create a mock, but it won't put it onto an application context, and won't substitute the beans of these classes that might have been created by spring.
So if this is what you want to achieve, you should use #MockBean instead.
This annotation belongs to Spring Testing framework rather than a plain mockito annotation.
All-in-all you might end up with something like this:
#SpringBootTest
class MyTest
{
#MockBean
BuilderRepository builderRepo;
#MockBean
AdminUserRepository adminUserRepo;
#Autowired // spring will inject your mock repository implementations
// automatically
BuilderServiceImpl underTest;
#Test
void mytest() {
...
}
}
Add #Autowire annotation Annotations on below fields. Error due your not initialized below object In BuilderServiceImpl
#Autowire
private final BuilderRepository builderRepository;
#Autowire
private final AdminUserRepository adminUserRepository;
Why are you creating BuildService manually? If you do this, set AutoMapper manualy too.
#BeforeEach
void setUp(){
autoCloseable = MockitoAnnotations.openMocks(this);
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
underTest.setAutoMapper(new AutoMapper());
}
You are not using di.

spring boot integration testing mock method returns null

I wrote simple spring boot application with Controller, Service and Business classes, but while writing integration test the mock method of Service is returning null
MockMainController
#RestController
public class MockMainController {
#Autowired
private MockBusiness mockBusiness;
#GetMapping("request")
public MockOutput mockRequest() {
return mockBusiness.businessLogic(new MockInput());
}
}
MockBusiness
#Service
public class MockBusiness {
#Autowired
private MockService mockService;
public MockOutput businessLogic(MockInput input) {
return mockService.serviceLogic(input);
}
}
MockService
#Service
public class MockService {
#Autowired
private MockUtil mockUtil;
public MockOutput serviceLogic(MockInput input) {
mockUtil.exchange(UriComponentsBuilder.fromUriString(" "), HttpMethod.GET, HttpEntity.EMPTY,
new ParameterizedTypeReference<MockOutput>() {
});
return new MockOutput();
}
}
I'm trying to mock the MockService bean in application context using #MockBean
MockControllerTest
#SpringBootTest
#ActiveProfiles("test")
#Profile("test")
#RunWith(SpringJUnit4ClassRunner.class)
public class MockControllerTest {
#Autowired
private MockMainController mockMainController;
#MockBean
private MockService mockService;
#Test
public void controllerTest() {
MockOutput output = mockMainController.mockRequest();
given(this.mockService.serviceLogic(ArgumentMatchers.any(MockInput.class)))
.willReturn(new MockOutput("hello", "success"));
System.out.println(output); //null
}
}
In the test method I created mock service bean using #MockBean I'm not having any error here but System.out.println(output); prints null
You are getting null because of wrong statements order in your test method. You first call controller method and you get what's inside default #MockBean which is in this case null. Swap statement:
MockOutput output = mockMainController.mockRequest();
with
given(this.mockService.serviceLogic(ArgumentMatchers.any(MockInput.class)))
.willReturn(new MockOutput("hello", "success"));
and you will get expected result.

Spring MVC Controller Test passing due to not finding model attribute

I have created the Home Controller below. This controller fetches the 5 dummy posts I have created in the "PostRepository" class through PostService class.
#Controller
public class HomeController {
#Autowired
PostService postService;
#RequestMapping("/")
public String getHome(Model model){
model.addAttribute("Post", postService);
return "home";
}
}
I have implemented the following test..
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebConfig.class})
#WebAppConfiguration
public class ControllerTest {
#Test //Test the Home Controller
public void TestHomePage() throws Exception{
HomeController homeController = new HomeController();
MockMvc mockMvc = standaloneSetup(homeController).build();
mockMvc.perform(get("/"))
.andExpect(view().name("home"))
.andExpect(model().attributeDoesNotExist("Post"));
}
}
The test has successfully passed. But the attribute should exist.
You are mixing two incompatible features of Spring's testing support.
If you instantiate the controller within the test, you need to use MockMvcBuilders.standaloneSetup().
If you are using the Spring TestContext Framework (i.e., #ContextConfiguration, etc.), then you need to use MockMvcBuilders.webAppContextSetup().
Thus, the following is the appropriate configuration for your test.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = WebConfig.class)
#WebAppConfiguration
public class ControllerTest {
#Autowired
WebApplicationContext wac;
#Autowired
PostService postService;
#Test
public void TestHomePage2() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
mockMvc.perform(get("/"))
.andExpect(view().name("home"))
.andExpect(model().attribute("Post",postService));
}
}
Regards,
Sam (author of the Spring TestContext Framework)
If that's the complete code, then you are missing
#RunWith(SpringJUnit4ClassRunner.class)

How to fix test failing with "No ModelAndView found"?

This class is in the top of my tests hierarchy:
#TestPropertySource("/test.properties")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public abstract class ApplicationAbstractTest {
}
And few more test classes:
#WebAppConfiguration
#ActiveProfiles("mysql")
abstract public class AbstractControllerTest extends ApplicationAbstractTest {
protected MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#PostConstruct
private void postConstruct() {
mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build();
}
}
JsonUserServiceTest:
#ActiveProfiles("json")
public class JsonUserServiceTest extends ApplicationAbstractTest {
#Before
public void setUp() throws Exception {
...
}
}
ContactControllerTest:
public class ContactControllerTest extends AbstractControllerTest {
#Test
public void testGet() throws Exception {
mockMvc.perform(get("/update-" + ID + "-contact")
.with(userAuth(USER)))
// .andExpect(status().isOk())
.andDo(print())
.andExpect(view().name("details"))
.andExpect(forwardedUrl("/WEB-INF/jsp/details.jsp"));
}
}
So, when I run ContactControllerTest along - it is successfull, and print method shows me:
Handler:
Type = com.telecom.web.ContactController
Method = public java.lang.String com.myApp.web.ContactController.details(java.lang.Integer,org.springframework.ui.ModelMap)
But when I run all tests, so JsonUserServiceTest runs first, ContactControllerTest fails. And print shows:
Handler:
Type = null
...
java.lang.AssertionError: No ModelAndView found
What is wrong in configuration? Or how troubleshoot it?
UPD:
at the same time, test like this, allways works fine:
public class UserControllerTest extends AbstractControllerTest {
#Test
public void testRegister() throws Exception {
mockMvc.perform(get("/register"))
.andDo(print())
.andExpect(view().name("profile"))
.andExpect(forwardedUrl("/WEB-INF/jsp/profile.jsp"));
}
}
UPD:
There is controller's method I'm testing:
#GetMapping("/update-{id}-contact")
public String details(#PathVariable Integer id, ModelMap model) {
Integer userId = AuthorizedUser.id();
LOG.info("get contact {} for User {}", id, userId);
Contact contact = service.get(id, userId);
model.addAttribute("contact", contact);
return "details";
}
I also have such bean:
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
UPD: I've tried configure mockMvc in separate class:
#Configuration
public class TestConfig {
#Autowired
private WebApplicationContext webApplicationContext;
#Bean
public MockMvc mockMvc() {
return MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build();
}
}
And added it here:
#WebAppConfiguration
#ContextConfiguration(classes = {TestConfig.class})
#ActiveProfiles("mysql")
abstract public class AbstractControllerTest extends ApplicationAbstractTest {
but I've received:
java.lang.IllegalStateException: springSecurityFilterChain cannot be
null. Ensure a Bean with the name springSecurityFilterChain
implementing Filter is present or inject the Filter to be used.
The WARN message doesn't cause the test cases to fail. It just says that Entity manager factory is registered twice. This will only be an issue if you cluster your application using the same Entity Manager Factory. For test case run it is not a cause for concern.
The root cause of the testcase failure is in these two lines
.andExpect(view().name("details"))
.andExpect(forwardedUrl("/WEB-INF/jsp/details.jsp"));
Please check if the project has a view named "details" and the forwardded url is "/WEB-INF/jsp/details.jsp"
Update
Could you please try this
#Configuration
public class TestConfig {
#Autowired
private Filter springSecurityFilterChain;
#Autowired
private WebApplicationContext webApplicationContext;
#Bean
public MockMvc mockMvc() {
return MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurityFilterChain)
.build();
}
}
Create a configuration file that will initialize mocking objects for your test cases. And put at all test case classes.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestConfig.class})
It will initialize all your mocking objects only once and cached after that and reused for all test cases.
Or if you don't want to use mocking configuration, you can directly
pass the actual application configuration to ContextConfiguration as
below
For annotation based application configuration (here AppConfig and AppConfig2 are your configuration class)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {AppConfig.class, AppConfig2.class})
For xml based application configuration (here appConfig.xml and appConfig2.xml are your configuration files)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:pathTo/appConfig.xml","classpath:pathTo/appConfig2.xml"})
Reference : JUnit + Spring integration example

Spring #Autowired constructor causes #Value to return null when instantiated in test class

I'm using an autowired constructor in a service that when instantiated in the test class causes the #Value annotations to return null. Autowiring the dependencies directly solves the problem but the project follows the convention of using constructor based autowiring. My understanding is that instantiating the service in the test class is not creating it from the Spring IoC container which causes #Value to return null. Is there a way to create the service from the IoC container using constructor based autowiring without having to directly access the application context?
Example Service:
#Component
public class UpdateService {
#Value("${update.success.table}")
private String successTable;
#Value("${update.failed.table}")
private String failedTable;
private UserService userService
#Autowired
public UpdateService(UserService userService) {
this.userService = userService;
}
}
Example Test Service:
#RunWith(SpringJUnite4ClassRunner.class)
#SpringApplicationConfiguration(classes = {TestApplication.class})
#WebAppConfiguration
public class UpdateServiceTest {
private UpdateService updateService;
#Mock
private UserService mockUserService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
updateService = new UpdateService(mockUserService);
}
}
To make #Value work updateService should be inside of spring context.
The best practice for spring framework integration tests is to include application context in test context and autowiring test source in test:
...
public class UpdateServiceTest {
#Autowired
private UpdateService updateService;
...
Mock userService
Option with changing userService to protected and considering that test and source classes are in same package.
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
updateService.userService = mockUserService;
}
Option with reflection with Whitebox:
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Whitebox.setInternalState(updateService, 'userService', mockUserService);
}
The #Value is filled by a property placeholder configurer which is a post processor in the spring context. As your UpdateService is not part of the context it is not processed.
Your setup looks a little like a unclear mixture of unit and integration test. For a unit tests you will not need a spring context at all . Simply make the #Value annotated members package protected and set them or use ReflectionTestUtils.setField() (both shown):
public class UpdateServiceTest {
#InjectMocks
private UpdateService updateService;
#Mock
private UserService mockUserService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(updateService, "successTable", "my_success");
updateService.failedTable = "my_failures";
}
}
For an integration test all wiring should be done by spring.
For this I added a inner config class providing the mock user service (the #Primary is only for the case you have any other user service in your context) and the mock is stored in a static member here to have simple access to the mock from the tests afterwards.
#RunWith(SpringJUnite4ClassRunner.class)
#SpringApplicationConfiguration(classes = {TestApplication.class, UpdateServiceTest.TestAddOn.class})
#WebAppConfiguration
public class UpdateServiceTest {
#Autowired
private UpdateService updateService;
private static UserService mockUserService;
static class TestAddOn {
#Bean
#Primary
UserService updateService() {
mockUserService = Mockito.mock(UserService.class);
return mockUserService;
}
}
}

Categories