Spring can't resolve dependencies with an internal class - java

today I've met online another poor soul learning Spring. I decided I'll help them. Story as old as Spring, a missing bean in unit tests. I made a quick fix, I put a configuration with the missing bean and it worked, seemed like everything was fine.
#Configuration
class Config {
#Bean
HelloService getHelloService() {
return new HelloService();
}
}
#ExtendWith(SpringExtension.class)
#WebMvcTest(HelloController.class)
#Import({Config.class})
class HelloControllerIntTest {
#Autowired
private MockMvc mvc;
#Test
void hello() throws Exception {
RequestBuilder request = get("/hello");
MvcResult result = mvc.perform(request).andReturn();
assertEquals("Hello, World", result.getResponse().getContentAsString());
}
#Test
public void testHelloWithName() throws Exception {
mvc.perform(get("/hello?name=Dan"))
.andExpect(content().string("Hello, Dan"));
}
}
On the second thought, polluting the public space with additional and very genericly named class is not a good idea, so I decided to put it inside of the class.
#ExtendWith(SpringExtension.class)
#WebMvcTest(HelloController.class)
#Import({HelloControllerIntTest.Config.class})
class HelloControllerIntTest {
#Configuration
static class Config {
#Bean
HelloService getHelloService() {
return new HelloService();
}
}
#Autowired
private MockMvc mvc;
#Test
void hello() throws Exception {
RequestBuilder request = get("/hello");
MvcResult result = mvc.perform(request).andReturn();
assertEquals("Hello, World", result.getResponse().getContentAsString());
}
#Test
public void testHelloWithName() throws Exception {
mvc.perform(get("/hello?name=Dan"))
.andExpect(content().string("Hello, Dan"));
}
}
To my surprise, it doesn't work, 404 error. I put a breakpoint in the HelloController and it seems the bean is not constructed at all. Also I peeked into the beans definitions and it seems the first version has 91 beans, and the second 88, so we have missing beans over there.
Any ideas what happened here? Why in the second version Spring ignores HelloController?

The reason why this happens is because your Config annotation is looking in a sub-package to find the beans but it can no longer find them.
If you annotate your Config static class with a #ComponentScan("package.of.your.helloController") then your controller it will be found again.

Related

Injecting mock before Spring's post-construct phase

Basically, the question is in the title.
I faced a problem that in post-construct phase my bean (that is autowired in the bean that is going through post-construct phase right now) is already mocked, but all the behavior described by Mockito.when() doesn't work, all the calls return null.
While searching I found this solution.
But is it possible to make it work without using any 3rd party libraries?
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#ContextConfiguration(classes = TestApplicationConfiguration.class)
public class ServiceTest {
#Autowired
#Qualifier("test")
private PCLPortType pclPortType;
#MockBean
private ClearingHelper сlearingHelper;
#MockBean
private OrganizationCacheRepository organizationCacheRepository;
#Before
public void setup() throws Exception{
OperationResultWithOrganizationSystemIdMappingList res = new OperationResultWithOrganizationSystemIdMappingList();
when(clearingHelper.getOrgIdSystemIdMapping(any(Keycloak.class))).thenReturn(res);
}
#Test
public void test() throws Exception{
pclPortType.call("123");
}
}
Test config:
#TestConfiguration
public class TestApplicationConfiguration {
#Bean(name = "test")
public PCLPortType pclPortTypeForTest() throws JAXBException {
...
}
#Bean
public Keycloak keycloak() {
return Mockito.mock(Keycloak.class);
}
}
Component where I want to get mocked beans:
#Component
public class OrganizationCacheJob {
private static final Logger logger =
LogManager.getLogger(OrganizationCacheJob.class);
private final ObjectFactory<Keycloak> factory;
private final ClearingHelper clearingHelper;
private final OrganizationCacheRepository organizationCacheRepository;
#Autowired
public OrganizationCacheJob(ObjectFactory<Keycloak> factory,
ClearingHelper clearingHelper,
OrganizationCacheRepository organizationCacheRepository) {
this.factory = factory;
this.clearingHelper = ClearingHelper;
this.organizationCacheRepository = organizationCacheRepository;
}
#PostConstruct
public void updateCacheRepository() {
doUpdateCacheRepository();
}
#Scheduled(cron = "${organization.cache.schedule}")
public void start() {
logger.info("Starting update organization cache.");
doUpdateCacheRepository();
logger.info("Job finished.");
}
private void doUpdateCacheRepository() {
try {
Keycloak keycloak = factory.getObject();
OperationResultWithOrganizationSystemIdMappingList orgIdSystemIdMapping = clearingHelper.getOrgIdSystemIdMapping(keycloak);
if (orgIdSystemIdMapping != null) {
orgIdSystemIdMapping.getContent().forEach(o -> organizationCacheRepository.saveOrgIdsSystemsIdsMappings(o.getOrgId(), o.getId()));
logger.debug("Was saved {} orgIds", orgIdSystemIdMapping.getContent().size());
}
} catch (Exception e) {
logger.error("Error fetching whole mapping for org and systems ids. Exception: {}", e);
}
}
}
So, in post-construct phase of OrganizationCacheJob I want to get res when calling clearingHelper, but instead I get null.
ClearingHelper is a regular Spring bean marked as a #Component with public methods.
Ahh ok I just realized - when you start your test case, whole env is up and running first, then you advance to testing phase. So, translating to your case - first you got injection and post-constructs called, then #Before method is done, thus the result.
So as you can see, code says more than all the words you could put in your original post.
If it is possible for you, use spies insteed of mocks. If it is not possible to construct that, you will have to redesign your tests to not rely on post construct.
In this case, since you want the same post-construct behavior for every test case, provide own factory method for given mock (like you did with keycloak) and move when-doReturn there. It will be guaranteed that it will happen before post construct.

Excluding an ApplicationListener #Component in Spring Boot during tests

I am trying to have my test unit up and running, and I have encountered a weird issue. My application uses an ApplicationListener class annotated as a #Component to perform an operation during startup.
During tests I have mocked the service that contains the logic, but I found that even though Mockito's when instructions work well in controller scope, the bean is not initialized for this ApplicationListener class: instead of returning what I define in the test unit, it returns either false or null - depending on the data type returned by each method in the service.
Since I have not found any way to initialize the mocked service from the test unit for the ApplicationListener class, I have decided to exclude it. To do so I have tried different approaches, being the one most often used that of creating a test application context and change its configuration. Unfortunately, nothing I have seen is working - so I am here asking for help. If possible, I would prefer not touching the ApplicationListener class and do all related coding in the test code.
I am interested in any of the two possible solutions, if they can be done:
1.- Get the mocked behaviour during the ApplicationListener execution, but I have read somewhere that this cannot be done
2.- Exclude the #Component from the test unit somehow.
TestUnit.Java:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class TestConfigurationService {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private MockService mockService;
private void initMockBean () throws Exception {
when(mockService.isDoingSomething()).thenReturn(true);
}
#Before
public void setup() throws Exception {
// Spring mock context application setup
this.mockMvc = webAppContextSetup(webApplicationContext).build();
// Initialize ConsulService mock bean
initMockBean ();
}
}
TestApplication.java
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan(basePackages="my.base.package", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = StartupConfiguration.class))
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
Besides what is shown in the code, I have also tried this annotation in file TestApplication.java:
#SpringBootApplication(exclude={StartupConfiguration.class})
StartupConfiguration.java
#Component
public class StartupConfiguration implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private ConfigurationService configurationService;
#Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
try {
configurationService.updateConfiguration();
} catch (Exception e) {
throw new RuntimeException ("Error", e);
}
}
}
ConfigurationService.java
public interface ConfigurationService {
public void updateConfiguration () throws Exception;
}
ConfigurationServiceImpl.java
#Service
#Transactional
public class ConfigurationServiceImpl implements ConfigurationService {
#Autowired
private MService mockService;
#Override
public void updateConfiguration() throws Exception {
if (mockService.isDoingSomething()==false)
throw new Exception ("Something went wrong");
}
}
Versions:
Spring Boot 1.5.4.RELEASE,
Java 1.8
You can create mock bean of the same type and mark it with #Primary annotation to replace real bean. You can achieve this by having test such configuration:
#Configuration
#Import(TestApplication.class)
public class TestConfiguration {
#Bean
#Primary
public ConfigurationService configurationService() {
return Mockito.mock(ConfigurationService.class);
}
}
then get this mock in test:
...
public class TestConfigurationService {
...
#Autowired
ConfigurationService configurationService;
#Before
public void setUp() {
when(mockService.isDoingSomething()).thenReturn(true);
}
}
Thanks, araxn1d. Your answer gave me the clue to solve this issue.
I mocked the StartupConfiguration class in TestUnit.java:
#MockBean
private StartupConfiguration startupConfiguration;
Though in this case I was lucky: application listeners don't have returning methods, so they don't need when test configuration. If I had required that some method there returned for example true or a value, this method would not apply.
But at least for application listeners, this is enough.

How to test if #Valid annotation is working?

I have the following unit test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {EqualblogApplication.class})
#WebAppConfiguration
#TestPropertySource("classpath:application-test.properties")
public class PostServiceTest {
// ...
#Test(expected = ConstraintViolationException.class)
public void testInvalidTitle() {
postService.save(new Post()); // no title
}
}
The code for save in PostService is:
public Post save(#Valid Post post) {
return postRepository.save(post);
}
The Post class is marked with #NotNull in most fields.
The problem is: no validation exception is thrown.
However, this happens only in testing. Using the application normally runs the validation and throws the exception.
Note: I would like to do it automatically (on save) and not by manually validating and then saving (since it's more realistic).
This solution works with Spring 5. It should work with Spring 4 as well. (I've tested it on Spring 5 and SpringBoot 2.0.0).
There are three things that have to be there:
in the test class, provide a bean for method validation (PostServiceTest in your example)
Like this:
#TestConfiguration
static class TestContextConfiguration {
#Bean
public MethodValidationPostProcessor bean() {
return new MethodValidationPostProcessor();
}
}
in the class that has #Valid annotations on method, you also need to annotate it with #Validated (org.springframework.validation.annotation.Validated) on the class level!
Like this:
#Validated
class PostService {
public Post save(#Valid Post post) {
return postRepository.save(post);
}
}
You have to have a Bean Validation 1.1 provider (such as Hibernate Validator 5.x) in the classpath. The actual provider will be autodetected by Spring and automatically adapted.
More details in MethodValidationPostProcessor documentation
Hope that helps
This is how I did it by loading ValidationAutoConfiguration.class into context:
#SpringBootTest
#ContextConfiguration(classes = { MyComponent.class, ValidationAutoConfiguration.class
public class MyComponentValidationTest {
#Autowired
private MyComponent myComponent;
#Test
void myValidationTest() {
String input = ...;
// static import from org.assertj.core.api.Assertions
assertThatThrownBy(() -> myComponent.myValidatedMethod(input))
.isInstanceOf(ConstraintViolationException.class)
.hasMessageContaining("my error message");
}
}
And MyComponent class:
#Component
#Validated
public class MyComponent {
public void myValidatedMethod(#Size(min = 1, max = 30) String input) {
// method body
}
)

Spring-test unexpectedly fails, how best triangulate the error?

This basic Spring test of a Spring 3 controller gives me a response code 404 result instead of the expected 200:
#RunWith(SpringJUnit4ClassRunner.class)
public class RootControllerMvcTest extends AbstractContextControllerTests {
private MockMvc mockMvc;
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(this.wac)
.alwaysExpect(status().isOk()).build();
}
#Test
public void viewIndex() throws Exception {
this.mockMvc.perform(get("/")).andExpect(view()
.name(containsString("index"))).andDo(print());
}
AbstractContextControllerTests:
#WebAppConfiguration("file:src/main/webapp/WEB-INF/spring/webmvc-config.xml")
#ContextConfiguration("file:src/main/resources/META-INF/spring/applicationContext.xml")
public class AbstractContextControllerTests {
#Autowired
protected WebApplicationContext wac; }
I have verified the controller method itself with another test, but when I use the context the test fails even as the controller does serve the proper page when run in container.
The controller in question looks like this:
#Controller
public class RootController {
#Autowired
CategoryService categoryService;
#RequestMapping(value = "/", method = RequestMethod.GET, produces = "text/html")
public String index(Model uiModel) {
uiModel.addAttribute("categories", categoryService.findAll());
return "index";
}
Clearly I'm not testing what I think. Any suggestion how to triangulate this issue?
I posted the full web mvc file at Pastebin to not clutter all space up here.
FYI: The value attribute for #WebAppConfiguration is not an XML configuration file but rather the root directory of your web application. So your current test configuration could never work.
Assuming that applicationContext.xml and webmvc-config.xml are the XML configuration files for your root and DispatcherServlet WebApplicationContexts, respectively, try redefining AbstractContextControllerTests as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy ({
#ContextConfiguration("/META-INF/spring/applicationContext.xml"),
#ContextConfiguration("file:src/main/webapp/WEB-INF/spring/webmvc-config.xml")
})
public abstract class AbstractContextControllerTests {
#Autowired
protected WebApplicationContext wac;
}
By the way, abstract test classes must actually be declared as abstract. ;)
Regards,
Sam (author of the Spring TestContext Framework)

Write JUnit test for #ExceptionHandler

I am writing a Rest service using Spring MVC. Here is the outline of the class:
#Controller
public class MyController{
#RequestMapping(..)
public void myMethod(...) throws NotAuthorizedException{...}
#ExceptionHandler(NotAuthorizedException.class)
#ResponseStatus(value=HttpStatus.UNAUTHORIZED, reason="blah")
public void handler(...){...}
}
I have written my unit tests using the design posted here. The test is basically as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(....)
public class mytest{
MockHttpServletRequest requestMock;
MockHttpServletResponse responseMock;
AnnotationMethodHandlerAdapter handlerAdapter;
#Before
public void setUp() {
requestMock = new MockHttpServletRequest();
requestMock.setContentType(MediaType.APPLICATION_JSON_VALUE);
requestMock.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
responseMock = new MockHttpServletResponse();
handlerAdapter = new AnnotationMethodHandlerAdapter();
}
#Test
public void testExceptionHandler(){
// setup ....
handlerAdapter.handle(...);
// verify
// I would like to do the following
assertThat(responseMock.getStatus(), is(HttpStatus.UNAUTHORIZED.value()));
}
}
However, the call to handle is throwing the NotAuthorizedException. I have read that this is by design to be able to unit test that the method throws the appropriate exception, however I would like to write an automated test that the framework is handling this exception appropriately and that the class under test has implemented the handler appropriately. Is there a way to do this?
Please be aware that I do not have access to the actual code in a place where I could post it.
Also, I am limited (for unfortunate reasons) to Spring 3.0.5 or 3.1.2.
Consider using Spring 3.2 and its mvc-test-framework
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml")
public class WebMvcTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getFoo() throws Exception {
this.mockMvc.perform(
get("/testx")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(status().isUnauthorized());
}
}
Controller code
#Controller
public class MyController {
public class MyException extends RuntimeException {
};
#RequestMapping("/testx")
public void myMethod() {
throw new MyException();
}
#ExceptionHandler(MyException.class)
#ResponseStatus(value = HttpStatus.UNAUTHORIZED, reason = "blah")
public void handler() {
System.out.println("handler processed");
}
}
This "test" passes well.
Disclaimer: currently I'm a noob in Spring MVC testing, actually it's my first test.
upd: Thanks to The Drake for the correction.
Annotate your Exception Handling controller with #ControllerAdvice instead of #Controller.
As Boris Treukhov noted when adding the #ExceptionHandler annotation to a method in the controller that throws the exception will make it work but only from that specific controller.
#ControllerAdvice will allow your exception handeling methods to be applicable for your whole application not just one specific controller.
You could change #Test to
#Test(expected=NotAuthorizedException.class)
This would return true if the internals throw up that exception and false otherwise.
This would also make the assertThat() unnecessary. You could write a second test that catches the NotAuthorizedException then you could inspect the responseMock under that condition then.

Categories