Unit test #WebMvcTest NoSuchBeanDefinitionException - spring-boot

I am writing a test for a very simple standalone Controller:
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
public class TestController {
#PostMapping("/version")
public String getVersion() {
// String a = TUtil.TestUtil("ds");
String a = "a";
return a;
}
}
This controller only defines and returns a String, it is not dependent on any other classes.
Here is my test:
#RunWith(SpringRunner.class)
#WebMvcTest(TestController.class)
public class TestControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void getVersion() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.post("/version"))
.andExpect(MockMvcResultMatchers.status().is4xxClientError())
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.content().string("a"))
.andReturn();
}
}
then it gives a lot of errors, some of them are:
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'ipArcuserauthMapper' defined in file [/Users/xy/Desktop/springweb/aaa/dccb/target/classes/com/dccb/tft/mapper/IpArcuserauthMapper.class]:
Invocation of init method failed;
nested exception is java.lang.IllegalArgumentException: Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
It says I need to define a bean of type 'IpArcuserauthMapper' which has nothing remotely to do with the TestController.
In another case, if I use the SpringbootTest annotation, the problem can be solved:
#SpringBootTest(classes = {TftApplication.class})
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
I have no idea what is going on with #WebMvcTest(TestController.class)??
From what I know the WebMvcTest annotation only scans the class defined in the brackets.
Please help !!

Related

Spring Testing Unsatisfied depencency NoSuchBeanDefinitionException

When I try to run my tests they all fail becaouse they can't find the bean of one of my classes.
Here are my codes which are used in the context:
The exception I get is this:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testProtoAdminController' : Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'TestProtoCopyService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
TestProtoAdminControllerTest
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = TestProtoAdminController.class)
public class TestProtoAdminControllerTest {
//Some used services
#Before
public void setUp() {
authenticatedMockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
#WithMockUser
public void testCopyProto() throws Exception {
authenticatedMockMvc.perform(post("/api/admin/{id}/copy", 1)
.contentType(MediaType.APPLICATION_JSON)
.content(asJson(new TestProtoBaseVo()))).andExpect(status().isOk());
}
//Some more tests which are not important in this case
TestProtoCopyService
#Service
public class TestProtoCopyServiceImpl implements TestProtoCopyService {
//Other services and repositories I have to use.
//Methods
}
TestProtoCopyService
public interface TestProtoCopyService {
#Transactional
void copyTestProto(long testProtoId, String sourceTenant, String targetTenant);
}
TestProtoAdminController
#RestController
#RequestMapping("/*")
public class TestProtoAdminController {
private TestProtoCopyService testProtoCopyService;
public TestProtoAdminController(TestProtoCopyService testProtoCopyService {
this.testProtoCopyService = testProtoCopyService;
}
When using #WebMvcTest Spring will prepare everything to test your web layer. This doesn't mean all your beans are scanned and are part of this test application context and ready to inject.
In general, you usually mock the service class of your controller with #MockBean and then use Mockito to specify its behavoir:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = TestProtoAdminController.class)
public class TestProtoAdminControllerTest {
#MockBean
private TestProtoCopyService mockedService
// the rest
#Before
public void setUp() {
authenticatedMockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
#WithMockUser
public void testCopyProto() throws Exception {
authenticatedMockMvc.perform(post("/api/admin/{id}/copy", 1)
.contentType(MediaType.APPLICATION_JSON)
.content(asJson(new TestProtoBaseVo()))).andExpect(status().isOk());
}
If you want Spring Boot to bootstrap the whole application context with every bean consider using #SpringBootTest. With this annotation, you can inject any bean to your application. The downside here is that you need to provide the whole infrastructure (database/queues/etc.) for your test.

How do I resolve this bean defninition override?

I've upgraded from Spring Boot 1.5 to Spring Boot 2.1.8. I had some tests that were working but are now failing.
I also was using maven-surefire plugin at version 2.9 and it worked, but I upgraded that to 2.22.0 as well, if that matters.
#ExtendWith(SpringExtension.class)
#WebMvcTest(value = ElementController.class, secure = false)
#ContextConfiguration(classes = TestSite1Config.class)
public class ElementControllerSite1IT {
#Autowired
protected MockMvc mvc;
#MockBean
ElementService elementService;
#BeforeEach
public void setup() {
when(elementService.getElementTable( ... )) //skipping args for brevity
.thenReturn(new ElementTable());
}
#Configuration
public static class TestSite1Config {
#Bean
#Autowired
public ElementController elementController(final ElementService elementService) {
return new ElementController(elementService, new ElementControllerProperties(DeploymentLocation.SITE1));
}
#Test
public void failSite1ValidationWithoutId() throws Exception {
ElementParameters params = getParams(false);
mvc.perform(post("/element")
.contentType(JSON)
.andExpect(status().isBadRequest());
}
//more tests, but doesn't matter.
}
There's another class like above, but replace Site1 with Site2.
There is an ElementController & Service class as well.
I get this exception:
Caused by BeanDefinitionOverrideException: Invalid bean definition with name 'elementController' defined in class path resource [ui/v2/web/ElementControllerSite1IT$TestSite1Config.class]: Cannot register bean definition [Root bean: class [null]; ... defined in class path resource [ui/v2/web/ElementControllerSite1ITConfig.class] for bean 'elementController': There is already [Generic bean: class [ui.v2.web.ElementController]; .. defined in file [...ui/v2/web/ElementController.class]] bound.
I didn't write the tests, it's code that I've inherited, in a code base that I'm just getting spooled up on.
You could try #TestPropertySource(properties ="..." :
#ExtendWith(SpringExtension.class)
#WebMvcTest(value = ElementController.class, secure = false)
#ContextConfiguration(classes = TestSite1Config.class)
#TestPropertySource(properties = {"spring.main.allow-bean-definition-overriding=true", "local.server.port=7777"})
public class ElementControllerSite1IT {
...
}
Add spring.main.allow-bean-definition-overriding=true to application.properties
Got it working with this: (for anyone who stumbles upon this question)
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
#WebMvcTest
#ContextConfiguration(classes = {ElementController.class,TestSite1Config.class})
public class ElementControllerSite1IT {
#Autowired
private MockMvc mvc;
...
#Configruation
public static class TestSite1Config {
#Bean
#Primary
public ElementControllerProperties elementControllerProperties() { return ... }
}
...
}

How to mock the #environment.getProperty('test.test') in thymeleaf test case

The code below
it is working perfectly fine in my application but I am having the test case for this thymeleaf html page as well it is throwing bean not found #envioronment.getProperty for my test case.
org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#environment.getProperty('css.specific.name')" (src/main/resources/templates/test_details.html)
EL1057E: No bean resolver registered in the context to resolve access to bean 'environment'
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ThymesheetTestSpringContext.class})
public class ViewTest {
#Autowired
private ThymeleafTestEngine testEngine;
#Autowired
private TestMessageSource messageSource;
String templatePath = "src/main/resources/templates/test_details.html";
#Test
public void shouldDisplayVehicleInformationForIVFP() throws Exception {
helper.assertLocalization("test", "returned", "#test");
helper.assertLocalization("test1", "Frame number:123", "#test1");
}
}
public class ThHelper {
private ThymeleafTestEngine testEngine;
private TestMessageSource messageSource;
.....................................
private Callable<HtmlElements> view = () -> testEngine.process(templatePath, model);
private Function<Map<String, Object>, HtmlElements> viewWithModel = (model) -> testEngine.process(templatePath, model);
}
You have to autowire your Environment bean so that it can have instantiated object like below.
#Autowired
Environment environment
You can also set #Value annotated fields through ReflectionUtils from spring test framework
A typical setField would look like as specified below:
ReflectionUtils.setField(mockedServiceWhichNeedsValueSet, nameOfFiledToBeSet, valueToBeSet);

Cucumber Spring and class configuration

I'm struggling with Cucumber and Spring configuration.
I'm writing selenium framework using Page Object Pattern, with BrowserFactory.
When I use #ComponentScan, #Component and #Autowire annotations everything works fine, but when I want to create a bit more complicated bean with #Bean annotation (BrowserFactory which registers few browser drivers) in #Configuration class it does not work, during debug I'm getting nulls on every single variable I'm trying to Autowire.
I'm using Spring 4.2.4, all cucumber dependencies in version 1.2.4.
Config:
#Configuration
public class AppConfig {
#Bean
#Scope("cucumber-glue")
public BrowserFactory browserFactory() {
BrowserFactory browserFactory = new BrowserFactory();
browserFactory.registerBrowser(new ChromeBrowser());
browserFactory.registerBrowser(new FirefoxBrowser());
return browserFactory;
}
#Bean(name = "loginPage")
#Scope("cucumber-glue")
public LoginPage loginPage() throws Exception {
return new LoginPage();
}
#Bean(name = "login")
#Scope("cucumber-glue")
public Login login() {
return new Login();
}
}
POP:
public class LoginPage extends Page {
public LoginPage() throws Exception {
super();
}
...
}
Page:
public class Page {
#Autowired
private BrowserFactory browserFactory;
public Page() throws Exception{
...
}
}
Login:
public class Login {
#Autowired
private LoginPage loginPage;
public Login(){}
...
}
Steps:
#ContextConfiguration(classes = {AppConfig.class})
public class LoginSteps {
#Autowired
Login login;
public LoginSteps(){
}
#Given("^an? (admin|user) logs in$")
public void adminLogsIn(Login.User user) throws Exception {
World.currentScenario().write("Logging in as " + user + "\n");
login.as(user);
}
}
Error:
cucumber.runtime.CucumberException: Error creating bean with name 'LoginSteps': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: Login LoginSteps.login; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'login': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private LoginPage Login.loginPage; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'loginPage' defined in AppConfig: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [LoginPage]: Factory method 'loginPage' threw exception; nested exception is java.lang.NullPointerException
And now for the fun part...
BrowserFactory in World class is properly Autowired!!
World:
public class World {
#Autowired
private BrowserFactory browserFactory;
...
}
So I'll answer my own question:)
Issue was that I was calling BrowserFactory inside of Page constructor.
Looks like this bean was not yet created and was causing NPEs.
In order to fix that I:
Added #Lazy annotation to configuration (all elements that use this configuration, both defined in that class and those which will be found by Scan will be created as Lazy)
Moved call to Browser Factory to #PostConstruct method
Two more things to increase readability of Spring config:
Added #ComponentScan to configuration
Classes with no constructor parameters are annotated with #Component and #Scope("cucumber-glue") annotation so bean creation can be removed from AppConfig.class

BeanCreationException on Spring TestNG PowerMock test

I'm getting BeanCreationException when using #AutoWired (Spring Annotation) and #PrepareForTest (PowerMock) and running my Spring enabled TestNG test.
I have a Spring controller, picked up via component scan.
I'm testing with TestNG, Mockito, and PowerMock.
I'm trying to spy on an auto wired controller thats autowired into the test, via: #AutoWired annotation.
Here's the beginning of the test class:
#PowerMockIgnore("*")
#WebAppConfiguration
#ContextConfiguration(locations = { "classpath:applicationContext-test.xml" })
#PrepareForTest(IWantAHamburgerController.class)
public class IWantAHamburgerControllerTest extends AbstractTestNGSpringContextTests {
#Autowired
private WebApplicationContext wac;
#Autowired
private MockHttpSession session;
#Autowired
private MockHttpServletRequest request;
#Autowired
private IWantAHamburgerController iWantAHamburgerController;
private MockMvc mockMvc;
#ObjectFactory
public IObjectFactory getObjectFactory() {
return new org.powermock.modules.testng.PowerMockObjectFactory();
}
#BeforeClass
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
// see the note at the bottom of this post about this line
//iWantAHamburgerController = (IWantAHamburgerController) applicationContext.getBean("iWantAHamburgerController");
}
I'm trying to test a GET method on IWantAHamburgerController. This is what the test looks like:
#Test
public void testGetHamburgerAfterAskingThisQuestion() throws Exception {
Principal p = PowerMockito.mock(Principal.class);
PowerMockito.when(p.getName()).thenReturn("oneofthefiveguys");
IWantAHamburgerController spy = PowerMockito.spy(iWantAHamburgerController);
PowerMockito.doReturn("oneofthefiveguys").when(spy).getUserName("oneofthefiveguys");
mockMvc.perform(get("/hamburgers/everythinghamburger.html")).andExpect(status().isOk())
.andExpect(view().name("jsp/hamburger/everythinghamburger"))
.andExpect(forwardedUrl("jsp/hamburger/everythinghamburger"));
PowerMockito.verifyPrivate(spy).invoke("initGrill", "oneofthefiveguys");
new org.mockito.internal.debugging.MockitoDebuggerImpl().printInvocations(spy);
}
Inside the test I want to spy on the autowired iWantAHamburgerController in order to verify that initGrill was called by the GET method on the controller.
If I remove #PrepareForTest(IWantAHamburgerController.class) I do not get a BeanCreationException, but then PowerMock doesn't work.
Note: I've tried to manually set the iWantAHamburgerController bean, but when I do, I get a ClassCastException.
Here's the full stack:
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name
'com.fiveguys.controllers.IWantAHamburgerControllerTest': Injection of
autowired dependencies failed; nested exception is
org.springframework.beans.factory.BeanCreationException: Could not
autowire field: private
com.fiveguys.controllers.IWantAHamburgerController
com.fiveguys.controllers.IWantAHamburgerControllerTest.iWantAHamburgerController;
nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type
[com.fiveguys.controllers.IWantAHamburgerController] found for
dependency: expected

Resources