Spring testing annotations - java

I've been working on a personal project very recently and looking at my test file I realized I have some regarding spring annotations:
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class BookingServicesTests {
private MockMvc mvc;
#Mock
private BookingRepository bookingRepository;
#InjectMocks
private BookingResource bookingController;
#Before
public void setup() {
JacksonTester.initFields(this, new ObjectMapper());
mvc = MockMvcBuilders
.standaloneSetup(bookingController)
.setControllerAdvice(new ConflictExceptionController())
.build();
}
...
}
So the thing is that #SpringBootTest is made to test your application using real HTTP methods. But in my setup method I included a MockMvcBuilders statement, which is a standalone test (no server and no application context).
My question is:
Are those elements incompatible?
One element obfuscate the other? This is: by using MockMvcBuilder can I get rid of #SpringBootTest?
Thanks

Use one or the other, not both. You are only allowed one JUnit's #runwith() and the value you pass in, whether it be SpringRunner.class or MockitoJUnitRunner.class, has very different behaviors.
So the code you posted is incorrect as #SpringBootTest will try to load the application context when your test class is "running with MockitoJUnitRunner". Therefore #SpringBootTest should be used along with #runWith(SpringRunner.class), as such
#RunWith(SpringRunner.class)
#WebMvcTest(BookingResource.class) // multiple controller class could go here
#AutoConfigureMockMvc
public class BookingServicesTests {
#Autowired
private MockMvc mvc;
#MockBean
private BookingRepository bookingRepository;
...
}
Notice how I replace #SpringBootTest() with #WebMvcTest(). This is because #WebMvcTest() only scans components that are #Controller and loads configuration for the web layer, whereas #SpringBootTest() does so for the entire application.
Or what you did with Mockito without Spring:
#RunWith(MockitoJUnitRunner.class)
public class BookingServicesTests {
private MockMvc mvc;
#Mock
private BookingRepository bookingRepository;
#InjectMocks
private BookingResource bookingController;
...
}

Related

Spring Boot unit test constructor injection

I'm using Spring Boot to create a REST API and write some unit tests on my controllers.
I know that the recommended manner to inject beans in spring is the constructor injection.
But when i add the #SpringBootTest annotation to my test class, I can not inject my controller class with constructor, I find myself obliged to use #Autowired.
Have some explanation and is there another way to use constructor injection with SpringBootTest.
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class PersonControllerTest {
#LocalServerPort
private int port;
#Autowired
private PersonController controller;
#Autowired
private TestRestTemplate restTemplate;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/cvtech/Persons/",
String.class)).contains("content");
}
#Test
public void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
#Test
void findAllByJob() {
}
}
For those using Kotlin, using field-injection means having to use lateinit var fields. Which is far from ideal.
It is possible to use constructor injection on SpringBoot tests however, using the #TestConstructor:
#ExtendWith(SpringExtension::class)
#TestConstructor(autowireMode = ALL)
#SpringBootTest(
classes = [MyApplication::class],
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
)
internal class MyIntegrationTest(
val beanA: BeanA,
#Qualifier("some qualified") val beanB: BeanB,
) {
...
// Tests can use the beans injected in the constructor without any problems
...
}
It's fine for your test to use field injection as the Test itself is not part of your domain; the test won't be part of your application context.
Also
You don't want to use SpringBootTest to test a controller, because that will wire ALL beans which can be way too heavy and time-consuming. Instead, you probably only want to create your controller and it's dependencies.
So your best option is to use #WebMvcTest which will only create the beans required for testing the specified controller.
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = PersonController.class)
class PersonControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
mockMvc.perform(get("/cvtech/Persons"))
.andExpect(status().isOk())
.andExpect(content().string(contains("content")));
}
}
Note that #WebMvcTest will search for a class annotated with #SpringBootConfiguration as it's default configuration. If it does not find it, or you want to manually specify some configuration classes, also annotate the test with #ContextConfiguration.
Also, as a sidenote, when using TestRestTemplate, you don't need to specify host and port. Just call restTemplate.getForObject("/cvtech/persons", String.class));
Same when using MockMvc or WebTestClient.

How to write junit test cases for rest controller, service and dao layer using springboot?

How to write JUnit Test cases for RestController, Service and DAO layer?
I've tried MockMvc
#RunWith(SpringRunner.class)
public class EmployeeControllerTest {
private MockMvc mockMvc;
private static List<Employee> employeeList;
#InjectMocks
EmployeeController employeeController;
#Mock
EmployeeRepository employeeRepository;
#Test
public void testGetAllEmployees() throws Exception {
Mockito.when(employeeRepository.findAll()).thenReturn(employeeList);
assertNotNull(employeeController.getAllEmployees());
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/employees"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
How can I verify the CRUD methods inside the rest controller and other layers ?
You can use #RunWith(MockitoJUnitRunner.class) for unit testing with your Service Layer mocking your DAO Layer components. You don't need SpringRunner.class for it.
Complete source code
#RunWith(MockitoJUnitRunner.class)
public class GatewayServiceImplTest {
#Mock
private GatewayRepository gatewayRepository;
#InjectMocks
private GatewayServiceImpl gatewayService;
#Test
public void create() {
val gateway = GatewayFactory.create(10);
when(gatewayRepository.save(gateway)).thenReturn(gateway);
gatewayService.create(gateway);
}
}
You can use #DataJpaTest for integration testing with
your DAO Layer
#RunWith(SpringRunner.class)
#DataJpaTest
public class GatewayRepositoryIntegrationTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private GatewayRepository gatewayRepository;
// write test cases here
}
Check this article for getting more details about testing with Spring Boot

Can't mock repository when testing with mockmvc

I have this quite simple controller class and a simple (jpa) repository.
What I want to do is to test it's api but mock it's repository and let it return an object or not depending on the test case.
My problem now is that I don't know how to do that.
I know how to mock a repository and inject it to a controller/service class with #Mock / #InjectMocks / when().return()
But I fail when I want to do the same after doing a request with MockMvc.
Any help is highly appreciated
The controller
import java.util.Optional;
#RestController
#Slf4j
public class ReceiptController implements ReceiptsApi {
#Autowired
private ReceiptRepository receiptRepository;
#Autowired
private ErrorResponseExceptionFactory exceptionFactory;
#Autowired
private ApiErrorResponseFactory errorResponseFactory;
#Override
public Receipt getReceipt(Long id) {
Optional<ReceiptEntity> result = receiptRepository.findById(id);
if (result.isEmpty()) {
throw invalid("id");
}
ReceiptEntity receipt = result.get();
return Receipt.builder().id(receipt.getId()).purchaseId(receipt.getPurchaseId()).payload(receipt.getHtml()).build();
}
private ErrorResponseException invalid(String paramName) {
return exceptionFactory.errorResponse(
errorResponseFactory.create(HttpStatus.NOT_FOUND.value(), "NOT_VALID", String.format("receipt with id %s not found.", paramName))
);
}
}
And it's test class
#WebMvcTest(ReceiptController.class)
#RestClientTest
public class ReceiptControllerTest {
#InjectMocks
private ReceiptController receiptController;
#Mock
private ReceiptRepository receiptRepository;
#Mock
private ErrorResponseExceptionFactory exceptionFactory;
#Mock
private ApiErrorResponseFactory errorResponseFactory;
private MockMvc mvc;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(
new ReceiptController())
.build();
}
#Test
public void getReceiptNotFoundByRequest() throws Exception {
mvc.perform(MockMvcRequestBuilders
.get("/receipt/1")
.header("host", "localhost:1348")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}
//TODO: Finish this test
#Test
public void getReceiptFoundByRequest() throws Exception {
ReceiptEntity receipt1 = ReceiptEntity.builder().id(99999L).purchaseId(432L).html("<html>").build();
when(receiptRepository.findById(1L)).thenReturn(Optional.of(ReceiptEntity.builder().id(1L).purchaseId(42L).html("<html></html>").build()));
ResultActions result = mvc.perform(get("/receipt/1")
.header("host", "localhost:1348")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
Within your setUp() method, you're using Mockito to mock the beans annotated with #Mock and inject them in a new instance of ReceiptController, which is then put into the field annotated with #InjectMocks.
On the next line, you're setting up MockMvc with a new instance of ReceiptController (you use new ReceiptController()), rather than using the instance that Mockito created. That means that all fields within that instance will be null.
This pretty much boils down exactly to Why is my Spring #Autowired field null.
To solve this, you could pass receiptController to MockMvc. In that case, you could also remove the #WebMvcTest and #RestClientTest as you're not using them.
Alternatively, you could setup your test with #RunWith(SpringRunner.class), and properly set up a Spring test by using #Autowired in stead of #InjectMocks and #MockBean in stead of #Mock. In that case, you don't need a setUp() method at all, as MockMvc could be autowired by Spring.
#WebMvcTest and MockMvc allows you to do integration testing, not unit testing.
They allow you to boot an actual Spring application (using a random port) and test the actual controller class with its dependencies. This means that the variables you declared at the top of your test are not actually used in your current setup.
If you wish to unit-test your controller, you can remove #WebMvcTest, create mock dependencies (like you did) and call your methods directly instead of using MockMvc.
If you really wish to write an integration test, you need to mock your external dependencies (the database for example). You can configure spring to use an embedded database (H2 for example) in the test environment, so that you do not affect your real database.
See an example here : https://www.baeldung.com/spring-testing-separate-data-source

Spring MVC Controller Unit Testing : How do I set private instance boolean field?

I have a Spring MVC REST controller class that has a private instance boolean field injected via #Value ,
#Value("${...property_name..}")
private boolean isFileIndex;
Now to unit test this controller class, I need to inject this boolean.
How do I do that with MockMvc?
I can use reflection but MockMvc instance doesn't give me underlying controller instance to pass to Field.setBoolean() method.
Test class runs without mocking or injecting this dependency with value always being false. I need to set it to true to cover all paths.
Set up looks like below.
#RunWith(SpringRunner.class)
#WebMvcTest(value=Controller.class,secure=false)
public class IndexControllerTest {
#Autowired
private MockMvc mockMvc;
....
}
You can use #TestPropertySource
#TestPropertySource(properties = {
"...property_name..=testValue",
})
#RunWith(SpringRunner.class)
#WebMvcTest(value=Controller.class,secure=false)
public class IndexControllerTest {
#Autowired
private MockMvc mockMvc;
}
You can also load your test properties form a file
#TestPropertySource(locations = "classpath:test.properties")
EDIT: Some other possible alternative
#RunWith(SpringRunner.class)
#WebMvcTest(value=Controller.class,secure=false)
public class IndexControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private Controller controllerUnderTheTest;
#Test
public void test(){
ReflectionTestUtils.setField(controllerUnderTheTest, "isFileIndex", Boolean.TRUE);
//..
}
}
My preferred option would be to set it in the constructor and annotate the constructor parameter with the #Value. You could then pass in whatever you want in the test.
See this answer

Unit test in Spring: injecting a dependency into a component under test

I have a very simple rest controller:
#RestController
public class MyController {
#Autowired
public Logger logger;
The logger dependency gets injected via the following configuration:
#Configuration
public class MyConfig {
#Bean
public Logger logger() {
return LoggerFactory.getLogger(MyController.class);
}
If I run the Spring application that contains the controller then everything works fine. However, I cannot manage to achieve this dependency injection when running my unit tests. In this case I have the following test configuration:
#Configuration
#Profile("test")
public class MyTestConfig {
#Bean
public Logger logger() {
return LoggerFactory.getLogger(MyCOntroller.class);
}
And this is the relevant part of my unit tests code:
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration(classes = MyTestConfig.class)
#ActiveProfiles("test")
public class MyContollerTest {
However the logger object does not get "autowired" in MyController (note that I do not want to mock the logger object), which results in a null pointer reference.
What am I missing?
A unit test shouldn't use any Spring configuration. You should simply instantiate your component, and inject dependencies (usually fake ones) manually.
You used field injection, which makes it a bit harder. With constructor injection, all you would need to do is
Logger logger = LoggerFactory.getLogger(MyController.class);
MyController controller = new MyController(logger);
Mockito can help injecting fake dependencies for you, though, even when using field injection, thanks to the #Mock, #Spy and #InjectMocks annotations:
#Spy
private Logger logger = LoggerFactory.getLogger(MyController.class);
#InjectMocks
private MyController controller;
#Before
public void prepare() {
MockitoAnnotations.initMocks(this);
}
That said, if I'm not mistaken, you're not using #RunWith(SpringJUnit4ClassRunner.class), so your test runner doesn't know anything about Spring, and thus doesn't create or use any Spring configuration.

Categories

Resources