I'm looking for way how to inject beans and mock in single constructor.
#Service
public class SomeService {
private EndpointUrlProvider endpointUrlProvider;
private RestTemplate restTemplate;
#Autowired
public SomeService(EndpointUrlProvider endpointUrlProvider, RestTemplate restTemplate){
this.endpointUrlProvider = endpointUrlProvider;
this.restTemplate = restTemplate;
}
Test:
class SomeTest extends Specification {
#Autowired
EndpointUrlProvider endpointUrlProvider
RestTemplate restTemplate = Mock {
postForEntity(_, _, SomeResponse.class) >> new ResponseEntity(new SomeResponse(), HttpStatus.OK)
}
SomeService someService = new SomeService(endpointUrlProvider, restTemplate)
//some tests
}
When I fire test my endpointUrlProvider in someService is null. What I did wrong ? What is the best way to test this?
As far as I see, you are trying to do partial mocking. To inject Spring beans, first you will need TestContextManager. Therefore, run the test with SpringRunner or SpringJUnit4ClassRunner. This should do the work:
#RunWith(SpringRunner.class)
public class SomeServiceTest {
#Autowired
private EndpointUrlProvider endpointUrlProvider;
#Before
public setUp() {
RestTemplate restTemplate = mock(RestTemplate.class);
SomeService someService = new SomeService(endpointUrlProvider, restTemplate);
}
}
Related
I need to write unit tests for a Spring Controller class.
The setup is like this:
#RestController
#RequestMapping("/")
public class MyCustomController {
#Autowired
private MagicWriter magicWriter;
#Autowired
private MagicUpdater magicUpdater;
#RequestMapping(path = "/", method = RequestMethod.POST)
public String postMagicMethod(#RequestParam(name = "SomeParam") String param1) {
var magicHandler = new MagicHandler(magicWriter, magicUpdater);
return magicHandler.doSomeMagic();
}
}
From my JUnit test, I need to use #MockBean for magicWriter and magicUpdater class.
So far I could not find anything constructive.
Here is my Unit test
#SpringJUnitConfig
#WebMvcTest(value= MyCustomController.class)
public class MyCustomControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private MagicWriter magicWriter;
#MockBean
private MagicUpdater magicUpdater;
#Autowired
private WebApplicationContext webApplicationContext;
#Configuration
static class Config {
#Bean
MyCustomController dispatchController() {
return new MyCustomController();
}
}
#Test
void basicTest() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
HttpHeaders headers = new HttpHeaders();
// Added some http headers
String uri = "/";
RequestBuilder request = MockMvcRequestBuilders.request(HttpMethod.POST, uri, headers);
MvcResult result = mockMvc.perform(request).andReturn();
assertThat(result.getResponse().getContentAsString()).isEqualTo(expected);
}
}
Convert your #Autowired parameters to be constructor based and not field-based.
#RestController
#RequestMapping("/")
public class MyCustomController {
private MagicWriter magicWriter;
private MagicUpdater magicUpdater;
#Autowired
public MyCustomController(MagicWriter magicWriter, MagicUpdater magicUpdater) {
this.magicWriter = magicWriter;
this.magicUpdater = magicUpdater;
}
// ... rest of your code
}
Then in your test, you just new an instance of this class with your mocks passed in. You're already resigned to using mock beans, so you don't need to whole Spring Context to come along.
// Unit test code example
MyCustomController testObject;
MagicWriter magicWriterMock;
magicUpdater magicUpdaterMock;
#BeforeEach
void setUp() throws Exception {
magicWriterMock = mock(MagicWriter.class);
magicUpdaterMock = mock(MagicUpdater.class);
testObject = new MyCustomController(magicWriterMock, magicUpdaterMock);
}
how can I create a "quasi" MVC integration test in Spring Boot. I would like to use my real implementation of a service, but I can't mange to do it. How can I inject real implementation instead of a mock.
My classes look like this
#Controller
#RequiredArgsConstructor
public class DashboardController {
private final RolesManagerService rolesManagerService;
private final ServletRequestManagerService servletRequestManagerService;
#GetMapping({"/", "/dashboard"})
public String index(Model model, HttpServletRequest httpServletRequest) {
model.addAttribute("canAddNewOrder", rolesManagerService.canRoleAccessApplicationPart(servletRequestManagerService.getRole(httpServletRequest), ApplicationPart.CREATE_NEW_ORDER));
model.addAttribute("var", "test");
return "dashboard";
}
}
and the test
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = DashboardController.class)
#AutoConfigureMockMvc
class IndexControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private UserDetailsService userDetailsService;
#MockBean
RolesManagerService rolesManagerService;
#MockBean
private ServletRequestManagerService servletRequestManagerService;
#Test
void testDashboard() throws Exception {
mockMvc.perform(get("/dashboard").with(user("admin").password("pass").roles("USER","ADMIN")))
.andExpect(status().isOk())
.andExpect(view().name("dashboard"))
.andExpect(xpath("//a").nodeCount(1))
.andExpect(model().attributeExists("canAddNewOrder"))
.andExpect(model().size(2))
.andExpect(model().attribute("var", equalTo("test")))
.andExpect(model().attribute("canAddNewOrder", equalTo(false)))
.andDo(print());
}
}
Generally WebMvcTest will not create a full Spring context with all components (Service etc) injected, but only the Controller you define. Either use a full SpringBootTest, or add something like this in your WebMvcTest class:
#TestConfiguration
static class AdditionalTestConfig {
#Bean
public RolesManagerService getService() {
return new RolesManagerService();
}
}
I've been trying to figure out why my mocked findIngredientsByCategory method is returning null when I have when(controller.findIngredientsByCategory(any()).thenReturn(Collections.emptyList()). This implementation works for the findAll method works.
Below is my implementation for my unit test:
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(IngredientController.class)
#ContextConfiguration(classes = {TestContext.class, WebApplicationContext.class})
#WebAppConfiguration
public class IngredientControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mvc;
#MockBean
private IngredientController ingredientController;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.webAppContextSetup(context).build();
}
#Autowired
private ObjectMapper mapper;
private static class Behavior {
IngredientController ingredientController;
public static Behavior set(IngredientController ingredientController) {
Behavior behavior = new Behavior();
behavior.ingredientController = ingredientController;
return behavior;
}
public Behavior hasNoIngredients() {
when(ingredientController.getAllIngredients()).thenReturn(Collections.emptyList());
when(ingredientController.getIngredientsByCategory(any())).thenReturn(Collections.emptyList());
when(ingredientController.getIngredientById(anyString())).thenReturn(Optional.empty());
return this;
}
}
#Test
public void getIngredientsByCategoryNoIngredients() throws Exception {
Behavior.set(ingredientController).hasNoIngredients();
MvcResult result = mvc.perform(get("/ingredients/filter=meat"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andReturn();
String content = result.getResponse().getContentAsString();
System.out.println(content);
}
And below is the implementation for the controller:
#RestController
#RequestMapping("/ingredients")
public class IngredientController {
#Autowired
private IngredientRepository repository;
#RequestMapping(value = "/filter", method = RequestMethod.GET)
public List getIngredientsByCategory(#RequestParam("category") String category) {
return repository.findByCategory(category);
}
}
I'm not sure why the mock controller is returning null with this request, when I tell it to return an empty list. If someone could please help with this I would greatly appreciate it! Thanks.
Th request path in test is "/ingredients/filter=meat", but it should be "/ingredients/filter?category=meat". So, it seem that getIngredientsByCategory was not called.
The MockMvc actually will call the IngredientController that is bootstrapped and created by the Spring Test framework but not call the mocked IngredientController that you annotated with #MockBean, so all the stubbing that you made will not be called.
Actually, the point of #WebMvcTest is to test #RestController and its related Spring configuration is configured properly , so a real instance of IngredientController is necessary to create rather than using a mocked one. Instead , you should mock the dependencies inside IngredientController (i.e IngredientRepository).
So , the codes should looks like:
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(IngredientController.class)
#ContextConfiguration(classes = {TestContext.class, WebApplicationContext.class})
#WebAppConfiguration
public class IngredientControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mvc;
#MockBean
private IngredientRepository ingredientRepository;
#Test
public void fooTest(){
when(ingredientRepository.findByCategory(any()).thenReturn(Collections.emptyList())
//And use the MockMvc to send a request to the controller,
//and then assert the returned MvcResult
}
}
I have a component which is auto wiring RestTemplate. I want to use that component for testing, but want to use TestRestTemplate. As I can see, TestRestTRemplate and RestTemplate is not connected through any interface.
RestTemplate implements RestOperations but TestRestTemplate doesn't. Is there elegant way to inject TestRestTemplate in RestTemplate, or some other alternate to achieve it?
I have created my own version of RestTemplate to use it in test and prod
#Autowired
private MyRestTemplate restTemplate;
public class MyRestTemplate {
#Autowired
private RestTemplate restTemplate;
}
#Configuration
#Profile("test")
public class MyConfig {
#Bean
public RestTemplate restTemplate() {
return new TestRestTemplate().getRestTemplate();
}
}
I am trying to run a Spring MVC Test but keep getting this exception.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
The exception is occuring because the autowired dependency,
#Autowired
private AccountService accountService;
is not getting injected in the test (works fine outside of the test).
Can anyone help me with this. Here is my code:
//AccountControllerITest Class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MockServletContext.class)
#WebAppConfiguration
public class AccountControllerITest {
private MockMvc mvc;
ObjectMapper om;
#Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
#Test
public void getAccounts() throws Exception {
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get("/api/accounts"))
.andExpect(status().isOk())
.andReturn();
}
}
}
//AccountController
#RestController
#RequestMapping("/api/accounts")
public class AccountController {
#Autowired
private AccountService accountService;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Set<AccountInfo>> getAccounts(#RequestParam(value = "firstName", required = false) String firstName,
#RequestParam(value = "surName", required = false) String surName) {
Set<AccountInfo> accounts = accountService.getAccounts(firstName, surName);
return new ResponseEntity<>(accounts, HttpStatus.OK);
}
}
Thanks for the help!
Because you are using standalone setup: mvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();. If you create controller via new AccountController(), Spring doesn't have a chance to inject accountService as it doesn't control instances creation and wiring.
You have two options:
Switch your test to unit test and do not use SpringJUnit4ClassRunner nor MockServletContext at
all. You can use #InjectMocks to inject private accountService:
public class AccountControllerITest {
private MockMvc mvc;
ObjectMapper om;
#Mock
private AccountController accountController = new AccountController();
#InjectMocks
private AccountService accountService = new Mockito.mock(AccountService.class);
#Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(accountController).build();
}
There is also enhancement you can apply to your controller. Replace field injection with constructor injection and you can pass accountService to controller via constructor in test. This way you don't need to use #InjectMocks at all.
Use webAppContext setup:
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeMethod
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
You need to configure your test to inject your autowired properties. See the following code block:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class AccountControllerITest {
// ... your test code
#Configuration
public static class YourConfiguration {
#Bean
AccountService accountService() {
// init and return your AccountService
}
}
}
You can init your AccountService with Mockito.