MockBean and MyBatis Mapper not working together (as they did before Spring Boot 2.2.7) - spring-boot

I am using MyBatis Spring Boot Starter version 2.1.3. Ever since moving to Spring Boot 2.2.7 (I've also tried 2.2.8 and 2.3.1 with the same results), I've had a problem using the MockBean annotation to mock a MyBatis interface (e.g. DAO). Let's say I have an interface like this one:
#Mapper
#Repository
public interface OrderDAO {
int insertOrder(#Param("order") Order order);
}
I'd like to execute an integration test and mock this mapper within my OrderService that contains a field of type OrderDAO.
My integration test is annotated with #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) and contains this field:
#MockBean
private OrderDAO orderDAO;
When I run a test such as this and look at what the references to the OrderDAO objects are, in my integration test I see this:
com.example.dao.OrderDAO$MockitoMock$1819884459
But inside my OrderService class the field is this:
com.sun.proxy.$Proxy179 (org.apache.ibatis.binding.MapperProxy#37d9310e)
So, calls to Mockito.verify obviously don't work because my mock has not been injected into my OrderService class. Now, very oddly, I found that adding this code makes everything work:
#TestConfiguration
static class MockConfig {
#Bean
public OrderDAO orderDAO() {
return Mockito.mock(OrderDAO.class);
}
}
Adding this nested class along with adding the ContextConfiguration annotation on the integration test class, and now the object that gets injected into the OrderService class is the MockitoMock -- the same object that is referenced by the MockBean annotated field in the test class. I didn't have to do this with Spring Boot 1.2.6 and earlier and I couldn't find any reference to a change that would have caused this (although perhaps I didn't search long enough).
So, I am wondering if I am doing something incorrectly, or, am I missing something I should be doing? It seems like this should just work like it did before, which the need for this extra nested TestConfiguration class. Appreciate any insights anyone can provide. Thanks.

As mentioned by ave in the comments, I had to add the name to the mock bean annotation to get it to work
#MockBean(name = "orderDAO")
private OrderDAO orderDAO;

Related

Spring Boot Test - Mocking A Handler Bean That Is Placed Deep In The Chain Of Responsibility

This should happen quite often:
RestController -> SomeClass -> SomeOtherClass -> YetAnotherClass and so on...
In my specific case there is a chain of responsibility which is injected to a rest controller. Each class is injected to it's previous class in the above chain.
I have implemented this with spring boot and I'm trying to test the REST resource. I want to Mock the "YetAnotherClass" so that when I send a request with MockMvc I can verify that something has happened in the mock object.
The problem is if I use #MockBean to mock YetAnotherClass then I have to inject it to SomeOtherClass. I have tried to inject it with #TestConfiguration but it seems that the Mock object injection doesn't work this way when the request is sent through MockMvc and the mock object is nested deep inside a chain such as above. (The original bean is injected not the mock one)
I know that JMockit mocks every instance of a class so it would solve my problem. But Spring boot defaults to Mockito and I prefer to avoid inconsistencies.
How can I implement such a test scenario?
I've run into a lot of annoyance using Mockito's annotation config setup when setting up Spring JUnit text fixtures.
I've found the way I like mocking beans with external integrations like this this by essentially having a separate MockObjectsConfig class with the mock objects I want using the standard Spring Context Configuration, and then import it alongside my real test config:
#Configuration
public class MockObjectsConfig {
#Bean
public YetAnotherClass yetAnotherClass() {
Mockito.mock(YetAnotherClass.class); // and add any thenReturns, answers, etc. here
}
... More mock beans...
}
Then include it in your test like so:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = { MyRealConfigClass.class, MockObjectsConfig.class)
public class MyJunitTest {
#Autowired
private RestController restController;
}
You can also annotate your mock bean with #Profile and test with #ActiveProfiles if you need to prevent a conflict there.
This way your mock YetAnotherClass will get injected into your context like all your other beans -- no relying on, mixing, and fiddling around with Mockito and other library annotations.

Unable to find a SpringBootConfiguration in Spring Boot Test 1.4

I'm not able to run a simple test in spring boot 1.4. I followed the tutorial from the official site testing-the-spring-mvc-slice but I didn't get it to work.
every time i get the following error:
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
any ideas, hints?
Thanks in advance
Edit:
this is the controller
#Controller
public class UserManagementController {
#GetMapping(value = "/gs/users/getUsers")
public #ResponseBody String getAllUsers() {
return "test";
}
}
this is the test
#RunWith(SpringRunner.class)
#WebMvcTest(UserManagementController.class)
public class UserManagementControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void showUserView() throws Exception {
this.mvc.perform(get("/gs/users/getUsers"))
.andExpect(status().isOk())
.andDo(print());
}
}
From my point of view it's exactly the same like this post from the site.
the #WebMvcTest will do:
Auto-configure Spring MVC, Jackson, Gson, Message converters etc.
Load relevant components (#Controller, #RestController, #JsonComponent etc)
Configure MockMVC
now why i need to configure a "super" class
The search algorithm works up from the package that contains the test
until it finds a #SpringBootApplication or #SpringBootConfiguration
annotated class. As long as you’ve structure your code in a sensible
way your main configuration is usually found.
So you have annotated your test with #*Test. It run, checked for configuration in subclasses, haven't found any, thrown an exception.
You have to have a config in a package or subpackage of test class or directly pass config class to #ContextConfiguration or #SpringBootTest or have class annotated with #SpringBootApplication.
According to #SpringBootApplication. I have tested controller in way you have mentioned with #WebMvcTest: it works if application has class annotated as #SpringBootApplication and fails with exception you've mentioned if not. There is remark it the article you mentioned:
In this example, we’ve omitted classes which means that the test will
first attempt to load #Configuration from any inner-classes, and if
that fails, it will search for your primary #SpringBootApplication
class.
Github discussion about the same point.
Spring Boot Documentation

Spring Annotation #WebMvcTest does not work in an app that has Jpa repositories

I have a Spring App that uses JPA repositories (CrudRepository interfaces). When I try to test my controller using the new Spring test syntax #WebMvcTest(MyController.class), it fails coz it tries to instantiate one of my service class that uses JPA Repository, does anyone has any clues on how to fix that? The app works when I run it.
Here is the error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.myapp.service.UserServiceImpl required a bean of type 'com.myapp.repository.UserRepository' that could not be found.
Action:
Consider defining a bean of type 'com.myapp.repository.UserRepository' in your configuration.
According to the doc
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. #Controller, #ControllerAdvice, #JsonComponent Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not #Component, #Service or #Repository beans).
This annotion only apply on the Spring MVC components.
If you are looking to load your full application configuration and use MockMVC, you should consider #SpringBootTest combined with #AutoConfigureMockMvc rather than this annotation.
I was able to unit test a Rest Controller by implementing junit 5 and using #SpringJUnitConfig along with #WebMvcTest. I am using Spring Boot 2.4.5 and this is my example:
#SpringJUnitConfig
#WebMvcTest(controllers = OrderController.class)
class OrderControllerTest {
#Autowired
private MockMvc mockMvc;
// This is a Mock bean of a Spring Feign client that calls an external Rest Api
#MockBean
private LoginServiceClient loginServiceClient;
// This is a Mock for a class which has several Spring Jpa repositories classes as dependencies
#MockBean
private OrderService orderService;
#DisplayName("should create an order")
#Test
void createOrder() throws Exception {
OrderEntity createdOrder = new OrderEntity("123")
when(orderService.createOrder(any(Order.class))).thenReturn(createdOrder);
mockMvc.perform(post("/api/v1/orders").contentType(MediaType.APPLICATION_JSON).content("{orderId:123}"))
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))TODO: here it will go the correlationId
.andExpect(jsonPath("$.orderId").value("123"));
}
}
Please only use #SpringBootTest when you are implementing integration tests.
I faced this same problem. Using #SpringBootTest and #AutoConfigureMockMvc worked perfectly for me.

Spring tries to autowire Mockito mocks

I have a spring managed application in which I like for my service layer to be mocked. So I created a Spring Application Java Config and returned a mock of the actual service.
For e.g,
#Bean
#Profile("resource")
public MyService mockService() {
return mock(MyService.class)
}
And then the MyService goes as
class MyService {
#Autowired
private MyDao dao;
}
When Spring creates bean of name "mockService", it also tries to Autowire MyDao on the mock ? This in my opinion defeats the purpose of mocking. Is this the expected behaviour, whats the workaround ?
So bottomline, its best practice to code to interfaces rather than concrete classes particularly if you are wanting to write focused tests on specific layers.
Well, I suppose you're trying to execute an integration test, but using some mocks, doing something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {Application.class, YourMockConfigurationClass.class})
#WebIntegrationTest
public class MyIntegrationTest {
...
}
In this case, you can create a mock of your service, and tell spring that this service instance has priority:
#Bean
#Primary
public MyService mockService() {
return mock(MyService.class)
}
Doing this, whenever Spring has to inject an unqualified MyService instance, it always chooses your mock. But it doesn't prevent Spring to create the original service.
So inside Spring context there will be two instances of MyService, the mock instance and the original implementation. But using #Primary you're telling Spring that your mock has priority.
On the other hand, in order to prevent Spring to load any mock in your environments (dev, test, etc...), you should annotate your mock configuration class either with a testing #Profile or a custom annotation (like #MockBean) and then configure your component scan strategy to not load this class (#ComponentScan(excludeFilters=#Filter(MockBean.class)))

How to use spring to resolve dependencies of an object created manually?

I would like to know if it's possible to use Spring to resolve the dependencies of an object created manually in my program. Take a look at the following class:
public class TestClass {
private MyDependency md;
public TestClass() {
}
...
public void methodThaUsesMyDependency() {
...
md.someMethod();
...
}
}
This TestClass is not a spring bean, but needs MyDependency, that is a spring bean. Is there some way I can inject this dependency through Spring, even if I instantiate TestClass with a new operator inside my code?
Thanks
Edit: The method I'm describing in my original answer below is the general way to accomplish DI external of the container. For your specific need - testing - I agree with DJ's answer. It's much more appropriate to use Spring's test support, for example:
#Test
#ContextConfiguration(locations = { "classpath*:**/applicationContext.xml" })
public class MyTest extends AbstractTestNGSpringContextTests {
#Resource
private MyDependency md;
#Test
public void myTest() {
...
While the above example is a TestNG test, there is also Junit support explained in 8.3.7.2. Context management and caching.
General approach: Annotate your class with #Configurable and utilize AspectJ load-time or compile-time weaving. See 6.8.1 in the Spring documentation on AOP for more details.
You can then annotate your instance variables with #Resource or #Autowired. Though they accomplish the same goal of dependency injection, I recommend using #Resource since it's a Java standard rather than Spring-specific.
Lastly, remember to consider using the transient keyword (or #Transient for JPA) if you plan on serializing or persisting the objects in the future. Chances are you don't want to serialize references to your DI'd repository, service, or component beans.
See the autowire() method on the AutowireCapableBeanFactory class. If you use an ClasspathXmlApplicationContext, you can get the factory with getAutowireCapableBeanFactory()
To get the ApplicationContext, you would need to use a static singleton or other central repository, such as JNDI or a Servlet container. See DefaultLocatorFactory on how to get an instance of the ApplicationContext.
If what you need is for testing purposes, Spring has good support for the scenario that you described above.
Check out Spring Reference manual section on Testing

Resources