I have a controller with multiple dependency which are solved by using spring configuration and Autowired in the controller class.
For Example:
#Controller
public class MyController{
#Autowired
private Type1 myDependency1;
#Autowired
private Type2 myDependency2;
}
I want to test this controller so that "mydependency1" is mocked and everything else is autowired.
How can I do this?
I was previously following following test:
#Mock
private Type1 myDependency1;
#InjectMocks
private Mycontroller controller = new MyController();
private MockMvc mockMvc;
#Before
public void setUp(){
mockMvc = standaloneSetup(controller).build();
}
But this is only returning the controller with mock of myDependency1 and not injecting myDependency2.
Alright after playing around with different mock tools, I gave up on the mock part and went back to profiles Function of spring.
I created a new profile called mockXYZ in my application-context.xml
And created the service i wanted to mock, or give a certain response as
#Service("type1")
#Profile("mockXYZ")
public class Type1Mock implements Type1{
....
}
And when testing, I made mockXYZ as my active profile, and used autowired my controller.
Like this I was able to mock only one dependency while other dependency working as normal, as they have only one implementation and would be selected for any profile.
Hope this helps others as well.
Thank you
Related
i am using spring tool suite to write a code .there are 4 layers restContoller,buisnesslogic,domain ,service....
i want to test for a method of business logic layer where it calls a method of dao which finally calls a method of service layer to return a simple primitive value... to make it clear in the businesslogic class i have autowired domain class ,and in the domain class i have autowired the service classs..the problem that i am facing iss when i run the test class i am getting NullPointerException i am attaching the code for the test class... kindly help if possible
#ExtendWith(MockitoExtension.class)
class CustomerBlTest {
#Mock
CustomerService mockService;
#Autowired
CustomerDO customerDo;
#Autowired
#InjectMocks
CustomerBl bl; //buisnesslogic class
#Test
void checkForGetInteger() {
when(mockService.getIntegerFfromService()).thenReturn(3);
int actual = bl.getInteger();
Assertions.assertEquals(3, actual);
}
}
Since you are extending MockitoExtension hence this test class is not aware of spring. But you are still using #Autowired annotation. So that's wrong. Remove all #AUtowired annotations in the test class. Besides this you do not need to bring in all the sterotyped classes. Bring in only the one that the class is using i.e. in your case the classes injected in CustomerBl class. I think that should be CustomerService class. So remove the CustomerDO class if it's not being used in CustomerBl class. The #InjectMock and #MOck annotation have been applied correclty. I think that should help you get your result.
You need to use #Mock instead of #Autowired as shown below.
#ExtendWith(MockitoExtension.class)
class CustomerBlTest {
#Mock
CustomerService mockService;
#Mock
CustomerDO customerDo;
#InjectMocks
CustomerBl bl; //buisnesslogic class
#Test
void checkForGetInteger() {
when(mockService.getIntegerFfromService()).thenReturn(3);
int actual = bl.getInteger();
Assertions.assertEquals(3, actual);
}
}
I have a controller and a test using #WebMvcTest and its running fine. Now i needed to add a little validation logic and for this i #Autowired an additional bean (a #Component, a MapstructMapper).
As expected now the test is failing due to #WebMvcTest. (No components are discovered)
Is there a way to add one bean to the context created?
Since i am using #MockBeans to mock service layer: is there a way to delegate all mock calls to a real object? With this i could mock the mapper and delegate to real mapper?!
A simple way of getting additional beans in the context is via using nested configuration classes within test classes
#TestConfiguration
static class AdditionalConfig {
#Bean
public SomeBean getSomeBean() {
return new SomeBean());
}
}
Example:
Scenario - If you have some Controller say ProductController and you have the corresponding slice-test for the class say ProductionControllerTest
#RestController
public class ProductController {
#Autowired
private IProductService productService;
#Autowired
private IProductValidator productValidator;
#GetMapping("product")
public Product getProduct(#RequestParam Long id) {
Product product = productService.getProduct(id); // you are using mockBean of productService
productValidator.validateProduct(product); // you need real bean of productValidator
return product;
}
}
Corresponding slide test class with an additional bean configuration
#RunWith(SpringRunner.class)
#WebMvcTest
public class ProductControllerSliceTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private IProductService productService;
#Autowired
private ApplicationContext applicationContext;
#TestConfiguration
static class AdditionalConfig {
#Bean
public IProductValidator productValidator() {
return new ProductValidator();
}
}
#Test
public void testProductGetById() throws Exception {
Product testProductWithID1L = new Product(1L, "testProduct");
when(productService.getProduct(anyLong())).thenReturn(testProductWithID1L);
mockMvc.perform(get("/product")
.param("id", "1")).andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("name")
.value("testProduct"))
.andExpect(MockMvcResultMatchers.jsonPath("id")
.value("1"));
}
}
Additional thoughts on your scenario: If you really intend to do the unit testing of the controller class then ideally you should mock all the additional dependencies of class that you are testing.
Ideally, the intention of the unit test is to only test the behavior of the object/class under test. All the dependent classes behavior or external calls should be mocked.
When you start testing several classes together under one test, you are moving more towards a component test or integration test
A very simple solution is to annotate your test class with #Import specifying the class(es) of the additional bean(s) you want to use in your test, as stated in documentation:
Typically #WebMvcTest is used in combination with #MockBean or #Import
to create any collaborators required by your #Controller beans.
e.g.
#WebMvcTest(MyController.class)
#Import(SomeOtherBean.class)
public class SourcingOrganisationControllerTests {
// The controller bean is made available via #WebMvcTest
#Autowired
private MyController myController;
// Additional beans (only one in this case) are made available via #Import
#Autowired
private SomeOtherBean someOtherBean;
}
I have a base test scenario that will be used by other integration tests. This scenario includes some mock beans (#MockBean) for external integrations.
Today, I have something like this in the integration test class:
#SpringBootTest
#WebAppConfiguration
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
#RunWith(SpringRunner.class)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OrderIT {
And the fields and annotations to prepare my integration test:
private MockMvc mockMvc;
#Autowired
private WebApplicationContext wac;
#Autowired
private ObjectMapper mapper;
#MockBean
private SomeGateway someGateway;
#MockBean
private SomeRabbitMqService someRabbitMqService ;
#MockBean
private AnotherRabbitMqService anotherRabbitMqService;
#MockBean
private SomeIntegrationService someIntegrationService ;
#MockBean
private Clock clock;
#Before
public void setup() {
//some methods mocking each service above, preparing mockMvc, etc
}
This scenario is necessary for use the MockMvc and create the main feature in the system, my Order. This Order is created by calling a POST method in a Rest API, saving the order in a memory database.
Even this working well, I need to duplicate this block of code containing these #MockBean and some #Autowired in another tests, because the Order is the base scenario to add Products to the order, set an Address to deliver, etc. Each scenario has a different integration test but all of them needs an Order.
So, how to share the "MockBeans" and the methods that mocks them among my Integration Tests? I had really bad experiences using inheritance among the tests and I really would like to try a different approach.
I end up using the Spring profiles.
I created a configuration class annotated with #Profile("test") and created the mocked beans there. Like:
#Profile("test")
#Configuration
public class MyMockConfiguration {
#Bean
public SomeService someService() {
SomeService someService = mock(SomeService .class);
// mocked methods and results
return someService ;
}
And in the Test class:
#ActiveProfiles("test")
#SpringBootTest
#WebAppConfiguration
#RunWith(SpringRunner.class)
public class MyControllerIT {
If some integration test needs to override the current mock implementation on the profile, the test just needs to declare #MockBean on the class and proceed on the mock as usual.
I'm not decide yet if test is a good name, because for me makes more sense mock the configuration by "context". So, instead of use the generic name test on the profile name, I could use createOrder and have different configuration profiles, each one with a different name and different mocks: createOrder, createOrderWithoutProducts.
I believe #ContextConfiguration was created for this purpose: https://spring.io/blog/2011/06/21/spring-3-1-m2-testing-with-configuration-classes-and-profiles
Environment :
Spring MVC 4
Junit
Mockito
Code :
Spring Service under test :
#Service("abhishekService")
public class AbhishekServiceImpl implements AbhisheskService {
#Autowired
private DaoOne daoOne;
#Autowired
private DaoTwo daoTwo;
#Autowired
private DaoThree daoThree;
#Autowired
private DaoFour daoThree;
}
Junit Test :
public class AbhishekServiceImplTest {
#Mock
private DaoOne daoOne;
#Mock
private DaoTwo daoTwo;
#Mock
private DaoThree daoThree;
#Mock
private UserDao userDao;
private AbhisheskService abhisheskService;
#Before
public void setUp(){
MockitoAnnotations.initMocks(this);
abhisheskService = new AbhishekServiceImpl();
}
}
Issue :
1)As shown in code snippet one , the class under test uses four dependencies.
2)As shown in code snippet two , in junit test case class , all 4 dependencies are mocked using #Mock
3)My question is : how these four mocked objects should be injected into test class ?
4)My class under test doesn't have constructor/setter injection but field injection using #Autowired.
5)I don't want to use #InjectMocks annotation due to its dangerous behavior
as mentioned here
Can anybody please guide on this ?
You are trying to test a class wrongly designed to test the behavior i.e. the properties are not accessible to be mocked. AbhishekServiceImpl has to provide a way to inject the mocks to the class. If you cannot access the fields then it is a clear case of wrongly designed class. Considering that the AbhishekServiceImpl is a class in a legacy code and you are trying to test the behaviour then you can use reflection to inject the mock objects as below:
DaoOne mockedDaoOne = mock(DaoOne.class);
when(mockedDaoOne.doSomething()).thenReturn("Mocked behaviour");
AbhishekService abhishekService = new AbhishekServiceImpl();
Field privateField = PrivateObject.class.getDeclaredField("daoOne");
privateField.setAccessible(true);
privateField.set(abhishekService, mockedDaoOne);
assertEquals("Mocked behaviour", abhishekService.doSomething());
Its very rare that you test behaviour of a class that you have not written yourself. Though I can imagine a use case where you have to test an external library because its author did not test it.
You can mark the junit test with #RunWith(SpringJUnit4ClassRunner.class) and then use #ContextConfiguration to define a context which instantiates the DAOs and service and wires them together.
I am trying to write a unit test for a REST controller which generates HATEOAS links via Resource assembler class. Everything is OK in production, but with the unit test Resource assembler class is not being injected into the controller.
my resource assembler class is:
#Component
public class ModelResourceAssembler extends ResourceAssemblerSupport<Model, ModelResource> {
public ModelResourceAssembler() {
super(ModelRestController.class, ModelResource.class);
}
#Bean
public ModelResourceAssembler modelResourceAssembler(){
return new ModelResourceAssembler();
}
#Override
public ModelResource toResource(Model model) {
...
}
}
The controller is:
#Controller
#RequestMapping("/demo")
#ComponentScan(basePackages = {"com.foo.demo"} )
public class ModelRestController {
#Autowired
private ModelPersistenceHandler modelPersistenceHandler;
#Autowired
private ModelResourceAssembler modelResourceAssembler;
...
}
And the unit test:
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes= {ModelResourceAssembler.class, ModelRestController.class})
public class ModelRestControllerTest {
private MockMvc mockMvc;
#InjectMocks
private ModelRestController modelRestController;
#Mock
private ModelPersistenceHandler modelPersistenceHandler;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(modelRestController).build();
}
...
}
No matter what I do the ModelResourceAssembler instance is always null. Since the application is Spring Boot it does not have the WebCoonfig classes and autowired WebApplicationContext is always null, so I cannot (and really don't want to since I am running a unit test) instantiate MockMvc via webAppContextSetup
The solution ended up being quite simple: I needed to add one line to my test:
#Spy
private ModelResourceAssembler modelResourceAssembler;
And the bean was instantiated and properly wired
In your example you use #InjectMocks but don't declare a mock for ModelResourceAssembler. You don't get an instance out of nowhere.
You use the MockitoJUnitRunner.class. It has no idea of Spring beans. For testing Spring applications you rather want to use SpringJUnit4ClassRunner.class.
If i may suggest, if you use constructor injection for your controller then you can just mock the dependency and not need spring junit test runner stuff.