How to test Spring controller using JUnit? - java

what is the flow to test spring controller using junit?
#Autowired
private PersonService personService;
#RequestMapping(value="/t2/{yy_id}/person", method=RequestMethod.GET)
#ResponseBody
public PersonInfo[] getPersons() {
return personService.getPersons();
}
Please give some example.

You can autowire your controller into a JUnit test and run the test with SpringJUnit4ClassRunner. Of course you would need to create a test context to initialise spring context (e.g. instantiation of test personService and autowiring it) for the test. Following might be a good start. And the next steps would be to check spring docs about testing.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:test-spring-context.xml")
public class YourControllerTest {
#Autowired
private PersonController personController;
#Test
public void testGetPersons() {
Assert.assertNotNull(personController.getPersons());
}
}

Related

Spring Boot #RestController #Autowired null in Unit tests

I can't understand why #Autowiring my #RestController class is returning null.
I want to do a basic unit test before doing an integrated test but its failing.
In fact anything that is being #Autowired is showing null in the test package.
I have a very simple test, I just want to see the basic works:
A very simple example:
#Component
public class SimpleComponent {
public int add(int a, int b){
return a + b;
}
}
And the test:
class SimpleComponentTest {
#Autowired
private SimpleComponent component;
#Test
public void givenSimpleComponentShouldNotBeNull(){
assertNotNull(component);//How on earth does this fail?
}
}
Here is the code from the Controller class:
#RestController
public class EmployeeAccountApi {
#PostMapping("/register")
public String register(){
return "Registering!";
}
}
public class EmployeeAccountApiUnitTest {
#Autowired
private EmployeeAccountApi employeeAccountApi;
#Test
public void testAutowiring(){
assertNotNull(employeeAccountApi);//Fails, Can't understand why
}
}
This works with the #Mock annotation:
But I dont want to mock it as that is the class I am unit testing.
I want to test that the method returns a basic string.
Question is why isn't it working?
#ExtendWith(MockitoExtension.class)
public class EmployeeAccountApiUnitTest {
#Mock
private EmployeeAccountApi employeeAccountApi;
#Test
public void testAutowiring(){
assertNotNull(employeeAccountApi);//This works
}
}
Even if I get the Employee Service that clearly worked and tested in the previous test also is null in this class:
public class EmployeeAccountApiUnitTest {
#Autowired
private EmployeeAccountApi employeeAccountApi;
#Autowired
private EmployeeService service;
#Test
public void testAutowiring(){
//assertNotNull(employeeAccountApi);
assertNotNull(service);
}
}
Why is #Autowired null in the Test?
Mocking is working fine
Plenty of similar questions but they are too specific and not provide any basic information
#Autowired only works if you are spinning up the application context for your application. Annotate the test class with #SpringBootTest, which tells the test to start the entire spring application context, and it should work.
#SpringBootTest has to be used on the test class for Spring to load your ApplicationContext and wire things up. Without out this there's nothing for Spring to inject.
You can use #WebMvcTest on the test class to focus your tests only on the Web Layer.
There's a whole bunch of annotations that allow you to "slice" your ApplicationContext up lots of other ways for testing various other parts of your application.
Spring's documentation and tutorials are really very good. You should check them out.
Testing the Web Layer

Spring Mock repository.count() always return 0

I have a spring boot project where I'm trying to mock my repository for tests.
I would like my repository.count() to return 30L but it actually always return 0
#Service
#Transactional
public class DishServiceImpl implements DishService {
private final DishRepository dishRepository;
public DishServiceImpl(DishRepository dishRepository) {
this.dishRepository = dishRepository;
}
#Override
public List<Dish> searchDishes() {
long countDish = dishRepository.count();
System.out.println(countDish);
[...]
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(classes = WhatAreWeEatingApp.class)
#Transactional
public class DishServiceTest{
#Mock
private DishRepository dishRepository;
#Autowired
private DishService dishService;
#Test
public void test(){
when(dishRepository.count()).thenReturn(30L);
dishService.searchDishes();
[...]
}
You repository mock is never set as dependency to the bean service.
Here you mock in the frame of a running Spring container :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = WhatAreWeEatingApp.class)
It is not unit test. So you want to use #MockBean from Spring Boot to mock a bean in the container, not #Mock from Mockito to mock instances created outside the container.
Don't like auto promotional post but this question should help you.
To go further you should not need to run a container to test the service method. So you should probably remove the Spring Boot test annotation and write a real unit test.

Is it good practice to use SpringRunner without SpringContext when writing JUnit test cases?

I tried junit with mockito, and wrote some test cases for a coding exercise.
Here is the test case which i wrote:
#RunWith(SpringRunner.class)
public class TransactionControllerTest {
#Mock
TransactionService transactionServiceMock;
#InjectMocks
TransactionController transactionController;
TransactionRequest txn = new TransactionRequest("123.34", "2018-11-28T23:32:36.312Z");
#Test
public void testSaveTxn() throws Exception {
Mockito.when(transactionServiceMock.saveTxn(Mockito.any(TransactionRequest.class))).thenReturn(true);
ResponseEntity<?> responseEntity = transactionController.saveTxn(null, txn);
assertTrue(responseEntity.getStatusCode().equals(HttpStatus.CREATED));
}
#Test
public void testGetStats() throws Exception {
StatsResponse sr = new StatsResponse("0.00", "0.00", "0.00", "0.00", 0L);
Mockito.when(transactionServiceMock.getStats()).thenReturn(sr);
ResponseEntity<StatsResponse> responseEntity = (ResponseEntity<StatsResponse>) transactionController.getStats(null);
System.out.println("sr response = "+responseEntity.getBody());
assertTrue(responseEntity.getBody().equals(sr));
}
#Test
public void testDelete() throws Exception {
Mockito.doNothing().when(transactionServiceMock).delete();
ResponseEntity<HttpStatus> responseEntity = (ResponseEntity<HttpStatus>) transactionController.deleteTxn(null);
System.out.println("sr response = "+responseEntity.getBody());
assertTrue(responseEntity.getStatusCode().equals(HttpStatus.NO_CONTENT));
}
}
The test cases were working fine.
But my application was rejected specifying the following reason:
You were using SpringRunner even though you are not using SpringContext in the tests, and mocking everything.
Now, following are my concerns:
What's wrong with the test cases?
What is the meaning of above rejection reason?
How can i correct that?
What's wrong with the test cases?
I think what they want you to do is to write a spring web layer test. This is not a spring MVC test/spring-boot test. Because you don't test the controller as a spring loaded resource. You test it as a simple java class. That won't prove whether it behaves as a Spring controller correctly. You won't be able to test features such as;
spring resource injection
request dispatching and validation
How can i correct that?
You have to write a spring MVC test and use MockMvc or RestTemplate to verify your controller. For example;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = YourContext.class)
#WebAppConfiguration
public class MyWebTests {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void foo() throws Exception {
mockMvc.perform(get("/status"));
//and verification
}
}
Usage of mockito mocks is not the worst idea, but you could have used auto wired #MockBeans.
If this is spring-boot, you will have more flexibility. Have a look at following resources.
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html
https://spring.io/guides/gs/testing-web/
You have complaint because you don't need spring's test features in your test.
Your test is pure unit test.
So if you will remove #RunWith(SpringRunner.class) nothing will be changed for your test. Just put there #ExtendWith(MockitoExtension.class)
SpringRunner will initialize spring context for you test that you could inject or mock slice of your application using following annotations:
#MockBean
#Autowired
etc..

Spring's #Retryable not working when running JUnit Test

I have this test:
#RunWith(MockitoJUnitRunner.class)
public class myServiceTest {
#InjectMocks
myService subject;
private myService spy;
#Before
public void before() {
spy = spy(subject);
}
#Test
public void testing() {
when(spy.print2()).thenThrow(new RuntimeException()).thenThrow(new RuntimeException()).thenReturn("completed");
spy.print1();
verify(spy, times(3)).print2();
}
and then I have:
#Service("myService")
public class myService extends myAbstractServiceClass {
public String print1() {
String temp = "";
temp = print2();
return temp;
}
#Retryable
public String print2() {
return "completed";
}
}
then I have this interface(which my abstractService implements):
public interface myServiceInterface {
#Retryable(maxAttempts = 3)
String print1() throws RuntimeException;
#Retryable(maxAttempts = 3)
String print2() throws RuntimeException;
}
but, I get a runtimeexception thrown when I run the test, leading me to believe it is not retrying. Am I doing this wrong?
This is because you are not using the SpringJUnitClassRunner.
Mockito and your own classes are not taking the #Retryable annotation in account. So you rely on the implementation of Spring to do so. But your test does not activate Spring.
This is from the SpringJUnit4ClassRunner JavaDoc:
SpringJUnit4ClassRunner is a custom extension of JUnit's BlockJUnit4ClassRunner which provides functionality of the Spring TestContext Framework to standard JUnit tests by means of the TestContextManager and associated support classes and annotations.
To use this class, simply annotate a JUnit 4 based test class with #RunWith(SpringJUnit4ClassRunner.class) or #RunWith(SpringRunner.class).
You should restructure your test class at least to something like:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=MyConfig.class)
public class MyServiceTest {
#Configuration
#EnableRetry
#Import(myService.class)
public static class MyConfig {}
...
What am I doing there?
activate the Spring JUnit hook
specify the Spring context configuration class
define the spring configuration and import your service as a bean
enable the retryable annotation
Are there some other pitfalls?
Yes, you are using Mockito to simulate an exception. If you want to test this behaviour with Spring like this, you should have a look at Springockito Annotations.
But be aware of that: Springockito you will replace the spring bean completely which forces you to proxy the call of your retryable. You need a structure like: test -> retryableService -> exceptionThrowingBean. Then you can use Springockito or what ever you like e.g. ReflectionTestUtils to configure the exceptionThrowingBean with the behaviour you like.
You should reference the interface type of your service in your test: MyServiceInterface
And last but not least. There is a naming convention nearly all Java developers follow: class names have first letter of each internal word capitalized
Hope that helps.
Another way:
#EnableRetry
#RunWith(SpringRunner.class)
#SpringBootTest(classes={ServiceToTest.class})
public class RetryableTest {
#Autowired
private ServiceToTest serviceToTest;
#MockBean
private ComponentInsideTestClass componentInsideTestClass;
#Test
public void retryableTest(){
serviceToTest.method();
}
}
I think you should let Spring manage the bean, create the appropriate proxy and handle the process.
If you want to mock specific beans, you can create mocks and inject them to the service under test.
1st option could be unwrapping proxied service, creating mocks and manually injecting them:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RetryConfiguration.class})
#DirtiesContext
public class TheServiceImplTest {
#Autowired
private TheService theService;
#Before
public void setUp(){
TheService serviceWithoutProxy = AopTestUtils.getUltimateTargetObject(theService);
RetryProperties mockRetryProperties = Mockito.mock(RetryProperties.class);
ReflectionTestUtils.setField(serviceWithoutProxy, "retryProperties", mockRetryProperties);
}
#Test
public void shouldFetch() {
Assert.assertNotNull(theService);
}
}
In this example, I mocked one bean, RetryProperties, and injected into the service. Also note that, in this approach you are modifying the test application context which is cached by Spring. This means that if you don't use #DirtiesContext, service will continue its way with mocked bean in other tests. You can read more here
Second option would be creating a test specific #Configuration and mock the depended bean there. Spring will pick up this new mocked bean instead of the original one:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RetryConfiguration.class, TheServiceImplSecondTest.TestConfiguration.class})
public class TheServiceImplSecondTest {
#Autowired
private TheService theService;
#Test
public void shouldFetch() {
Assert.assertNotNull(theService);
}
#Configuration
static class TestConfiguration {
#Bean
public RetryProperties retryProperties() {
return Mockito.mock(RetryProperties.class);
}
}
}
In this example, we have defined a test specific configuration and added it to the #ContextConfiguration.

Unit Test of file upload using MockMvcBuilders with standalone context and SpringBoot 1.2.5

I'm using Spring Boot 1.2.5-RELEASE. I have a controller that receive a MultipartFile and a String
#RestController
#RequestMapping("file-upload")
public class MyRESTController {
#Autowired
private AService aService;
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void fileUpload(
#RequestParam(value = "file", required = true) final MultipartFile file,
#RequestParam(value = "something", required = true) final String something) {
aService.doSomethingOnDBWith(file, value);
}
}
Now, the service works well. I tested it with PostMan and eveything goes as expected.
Unfortunately, I cannot write a standalone unit test for that code. The current unit test is:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#WebAppConfiguration
public class ControllerTest{
MockMvc mockMvc;
#Mock
AService aService;
#InjectMocks
MyRESTController controller;
#Before public void setUp(){
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void testFileUpload() throws Exception{
final File file = getFileFromResource(fileName);
//File is correctly loaded
final MockMultipartFile multipartFile = new MockMultipartFile("aMultiPartFile.txt", new FileInputStream(file));
doNothing().when(aService).doSomethingOnDBWith(any(MultipartFile.class), any(String.class));
mockMvc.perform(
post("/file-upload")
.requestAttr("file", multipartFile.getBytes())
.requestAttr("something", ":(")
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE))
.andExpect(status().isCreated());
}
}
Test fails with
java.lang.IllegalArgumentException: Expected MultipartHttpServletRequest: is a MultipartResolver configured?
Now, in the MultipartAutoConfiguration class from Spring Boot I see that a MultipartResolver is auto configured. But, I guess that with the standaloneSetup of MockMvcBuilders I cannot access this.
I tried several configurations of the unit test that I don't report for brevity. Especially, I also tried rest-assured as shown here, but honestly this doesn't work because it seems that I cannot mock the AService instance.
Any solution?
You are trying to combine here unit test (standaloneSetup(controller).build();) with Spring integration test (#RunWith(SpringJUnit4ClassRunner.class)).
Do one or the other.
Integration test will need to use something like code below. The problem would be faking of beans. There are ways to fake such bean with #Primary annotation and #Profile annotation (you create testing bean which will override main production bean). I have some examples of such faking of Spring beans (e.g. this bean is replaced by this bean in this test).
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeMethod
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
Secodn option is to remove #RunWith(SpringJUnit4ClassRunner.class) and other class level configuration on your test and test controller without Spring Context with standalone setup. That way you can't test validation annotations on your controller, but you can use Spring MVC annotations. Advantage is possibility to fake beans via Mockito (e.g. via InjectMocks and Mock annotations)
I mixed what lkrnak suggested and Mockito #Spy functionality. I use REST-Assured to do the call. So, I did as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#WebAppConfiguration
#IntegrationTest({"server.port:0"})
public class ControllerTest{
{
System.setProperty("spring.profiles.active", "unit-test");
}
#Autowired
#Spy
AService aService;
#Autowired
#InjectMocks
MyRESTController controller;
#Value("${local.server.port}")
int port;
#Before public void setUp(){
RestAssured.port = port;
MockitoAnnotations.initMocks(this);
}
#Test
public void testFileUpload() throws Exception{
final File file = getFileFromResource(fileName);
doNothing().when(aService)
.doSomethingOnDBWith(any(MultipartFile.class), any(String.class));
given()
.multiPart("file", file)
.multiPart("something", ":(")
.when().post("/file-upload")
.then().(HttpStatus.CREATED.value());
}
}
the service is defined as
#Profile("unit-test")
#Primary
#Service
public class MockAService implements AService {
//empty methods implementation
}
The error says the request is not a multi-part request. In other words at that point it's expected to have been parsed. However in a MockMvc test there is no actual request. It's just mock request and response. So you'll need to use perform.fileUpload(...) in order to set up a mock file upload request.

Categories