I want to test outgoing HTTP calls from my service using MockRestServiceServer. I got it working using following code:
#SpringBootTest
class CommentFetcherTest {
#Autowired
RestTemplate restTemplate;
#Autowired
CommentFetcher commentFetcher;
#Test
public void shouldCallExternalService(){
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(ExpectedCount.once(), requestTo("/test/endpoint")).andExpect(method(HttpMethod.GET));
//when
commentFetcher.fetchData();
mockServer.verify();
}
}
However the problem I run into is that RestTemplate is a bean shared between all tests from suite what makes impossible to further run integration tests which should be calling external services. This results in :
java.lang.AssertionError: No further requests expected
How is it possible to use MockRestServiceServer only for subset of tests?
I can't get rid of Dependency Injection through #Autowired and construct my service in tests such as
RestTemplate restTemplate = new RestTemplate();
CommentFetcher commentFetcher = new CommentFetcher(restTemplate);
As this would not read properties from application.properties necessary to proper functioning of my service.
Spring Boot provides a convenient way to test your client classes that make use of Spring's RestTemplate using #RestClientTest.
With this annotation, you get a Spring Test Context that only contains relevant beans and not your whole application context (which is what you currently get with #SpringBootTest).
The MockRestServiceServer is also part of this context and the RestTemplate is configured to target it.
A basic test setup can look like the following:
#RestClientTest(CommentFetcher.class)
class UserClientTest {
#Autowired
private CommentFetcher userClient;
#Autowired
private MockRestServiceServer mockRestServiceServer;
// ...
}
What's left is to stub the HTTP method call and then invoke the method on your class under test (commentFetcher.fetchData(); in your case).
Refer to this article if you want to understand more about how #RestClientTest can be used to test your RestTemplate usage.
Related
I'm attempting something perhaps misguided but please help.
I would like to test springboot controller via #WebFluxTest. However I would like to use WebClient instead of WebTestClient. How can this be done?
So far, I managed to use reflection to get ExchangeFunction out of WebTestClient and assign it to WebClient - and it works! Calls are made, controller responds - wonderful. However I don't think this is good approach. Is there a better way?
Thank you.
Ideally, you should use a WebTestClient which is more of a convenience wrapper around the WebClient. Just like the TestRestTemplate is for a RestTemplate. Both allow a request to be created and you can easily make assertions. They exist to make your test life easier.
If you really want to use a WebClient instead of a WebTestClient and do the assertions manually (which means you are probably complicating things) you can use the WebClient.Builder to create one. Spring Boot will automatically configure one and you can simply autowire it in your test and call the build method.
#SpringBootTest
public void YourTest {
#Autowired
private WebClient.Builder webClientBuilder;
#Test
public void doTest() {
WebClient webClient = webClientBuilder.build();
}
}
The same should work with #WebFluxTest as well.
Ok, after much experimentation here is a solution to test springboot controller & filters via a mocked connection - no webservice, no ports and quick test.
Unfortunately I didn't work out how to do it via #WebFluxTest and WebClient, instead MockMvc can be used to achieve desired result:
#ExtendWith(SpringExtension.class)
#Import({SomeDependencyService.class, SomeFilter.class})
#WebMvcTest(controllers = SomeController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class)
#AutoConfigureMockMvc()
public class SomeControllerTest {
#MockBean
private SomeDependencyService someDependencyService;
#Autowired
private MockMvc mockMvc;
private SomeCustomizedClient subject;
#BeforeEach
public void setUp() {
subject = buildClient();
WebClient webClient = mockClientConnection();
subject.setWebClient(webClient);
}
private WebClient mockClientConnection() {
MockMvcHttpConnector mockMvcHttpConnector = new MockMvcHttpConnector(mockMvc);
WebClient webClient = WebClient.builder().clientConnector(mockMvcHttpConnector).build();
return webClient;
}
#Test
public void sample() {
when(SomeDependencyService.somePersistentOperation(any(), any())).thenReturn(new someDummyData());
SomeDeserializedObject actual = subject.someCallToControllerEndpoint("example param");
assertThat(actual.getData).isEquals("expected data");
}
}
Now it is possible to test your customized client (for example if you have internal java client that contains few important customization like security, etags, logging, de-serialization and uniform error handling) and associated controller (and filters if you #import them along) at the cost of a unit test.
You do NOT have to bring up entire service to verify the client and controller is working correctly.
I am new to spring boot and Junit testing. I am facing problem in testing Rest Api's Get and Post method. Many tutorials online guide about the use of mockMvc in JUnit testing. But are those still valid for JUnit 5.
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = RegisterRestController.class)
class RegisterRestControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private RegisterUseCase registerUseCase;
#Test
void whenValidInput_thenReturns200() throws Exception {
mockMvc.perform(...);
}
}
However I read a article that you don't need to include #ExtendWith extension from SpringBoot 2.1 onwards.
Since Junit 5 and spring boot both updated with lot of changes from previous version it has become difficult to analyse which code available online is correct regarding testing.
I am not able to use MockMvc.perform().andExpect(status().isOk()).
mockMvc.perform(post("/forums/{forumId}/register", 42L)
.contentType("application/json")
.param("sendWelcomeMail", "true")
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isOk());
What is the correct use of testing Get and post methods. And testing Rest verbs comes under unit testing or integration testing?
Also guide me good source to check updated features of Junit 5 with Spring Boot(JUnit 5 doc is not helpful in my case).
You can use the MockMvcRequestBuilders inside your unit test. Here is an example with post.
#ExtendWith(SpringExtension.class)
#WebMvcTest(RegisterRestController.class)
#AutoConfigureMockMvc(addFilters = false) //for disabling secruity, remove if not needed
class RegisterRestControllerTest {
#MockBean
private MyService myService;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private MockMvc mockMvc;
#Test
void someTest() {
// mock post of new User
when(myService.createPojo(User.class)))
.thenAnswer((Answer<User>)
invocationOnMock -> invocationOnMock.getArgument(0, User.class));
// test post request with data
mockMvc.perform(MockMvcRequestBuilders
.post("/forums/{forumId}/register", 42L)
.contentType("application/json")
.param("sendWelcomeMail", "true")
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("some name"));
}
Similarly you can test GET, PUT and DELETE. Hope that helps you get into testing your controller.
To answer the unit vs. integration test part: In this case, you are testing a unit, the controller. The service, that handles the transaction and all other layers are mocked, eg with mockito in my example code.
For an integration test, you wouldn't use any mocks. And you would have a real database or at least a "fake one" like h2.
Here is my idea, I try to test my Restful controller with MockMvc
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(post(...).param(..))
.andExpect(...);
The API I call fires a rabbitmq message. But I don't want to test Amqp in the test. Instead, I create a mock producer like this:
#Mock
private AmqpProducer producer
I want to inject this producer into the spring context, so I can capture the method call producer.sendMessage and test the message.
To mock a bean in the Spring Boot contexte you cannot use directly #Mock.
It will create a mock for AmqpProducer but not which used by your container.
With Spring, the classical way to do that is annotating your test class with a specific context configuration class or file (#ContextConfiguration(...)) that provides the mock.
With Spring Boot, it is simpler : annotating your class with #WebMvcTest
and your field to mock with #MockBean is enough to mock the bean in the container (Spring guide).
Note that #WebMvcTest with a specified controller class specified in the annotation value will instantiate the specified controller and also all its direct dependencies declared. So you should mock all of them and not only which one that interests you in your unit test.
So it should look like :
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class WebMockTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private AmqpProducer producer;
#Test
public void foo() throws Exception {
this.mockMvc.perform(...);
verify(producer).sendMessage(expected);
}
}
We'are imlementing part of our security at service layer, so I add #PreAuthorize annotation to some methods of MyService.
At MyServiceSecurityTest I want to test only security role-permission matrix, without any business logic. For that reason I have to mock MyService. the problem is that both Mockito and Spring security use CGLIB proxies, and my service is not enhanced with #PreAuthorize after Mockito.mock(MyService.class).
Is there any way to mock service and preserve #PreAuthorize logic?
Example:
#Service
public class MyService implements IMyService {
#Override
#PreAuthorize("hasAuthority('SYSOP')")
public void someMethod(ComplexDTO dto) {
// lots of logic and dependencies, require lots of stubbing.
}
}
In order to avoid initialization of all dependencies of MyService#someMethod and building ComplexDTO at MyServiceSecurityTest I want to mock MyServiceSecurityTest but preserve #PreAuthorize checks.
You need to do integration tests and not unit tests. In general, you do not see mock classes in integration tests, at least you would not mock the class you are testing, in this I case I guess its the MyService class.
Setting up integration tests involves reading up on, but the short example below should get you on the right path
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("myProfile")
public class MyServiceIT {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Autowired
private TestRestTemplate restTemplate;
#Test
public void testMyService() {
logger.info("testMyService");
//user TestRestTemplate to call your service.
}
}
EDIT: In this integration test, Spring boots up normally. That means all the annotations for security are processed and all the beans it needs to create are created and properly injected. One thing you may have to control is the Spring profile.... that can be done with the #ActiveProfiles("myProfile") annotation, which I just added to the example.
i have a #Service that I am trying to mock in an Unit Test but i get a null value so far. In the application class I specify what are the scanBasePackages. Do I have to do this in a different way? Thanks.
This is my service class that implements an interface:
#Service
public class DeviceService implements DeviceServiceDao {
private List<Device> devices;
#Override
public List<Device> getDevices(long homeId) {
return devices;
}
}
This is my unit test.
public class SmartHomeControllerTest {
private RestTemplate restTemplate = new RestTemplate();
private static final String BASE_URL = “..”;
#Mock
private DeviceService deviceService;
#Test
public void getHomeRegisteredDevices() throws Exception {
Device activeDevice = new DeviceBuilder()
.getActiveDevice(true)
.getName("Alexa")
.getDeviceId(1)
.getHomeId(1)
.build();
Device inativeDevice = new DeviceBuilder()
.getInactiveDevice(false)
.getName("Heater")
.getDeviceId(2)
.getHomeId(1)
.build();
UriComponentsBuilder builder = UriComponentsBuilder
.fromUriString(BASE_URL + "/1/devices");
List response = restTemplate.getForObject(builder.toUriString(), List.class);
verify(deviceService, times(1)).getDevices(1);
verifyNoMoreInteractions(deviceService);
}
You have to use a Spring test runner if you want to load and use a Spring context during tests execution.
You don't specify any runner, so it uses by default the runner of your test API. Here is probably JUnit or TestNG (the runner using depends on the #Test annotation specified).
Besides, according to the logic of your test, you want to invoke the "real"
REST service :
List response = restTemplate.getForObject(builder.toUriString(),
List.class);
To achieve it, you should load the Spring context and load the Spring Boot container by annotating the test with #SpringBootTest.
If you use a Spring Boot context, to mock the dependency in the Spring context, you must not use #Mock from Mockito but #MockBean from Spring Boot.
To understand the difference between the two, you may refer to this question.
Note that if you are using the #SpringBootTest annotation, a TestRestTemplate is automatically available and can be autowired into your test.
But beware, this is fault tolerant. It may be suitable or not according to your tests.
So your code could look like :
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SmartHomeControllerTest {
private static final String BASE_URL = “..”;
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private DeviceService deviceService;
#Test
public void getHomeRegisteredDevices() throws Exception {
...
}
As a side note, avoid using raw type as List but favor generic type.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = NotificationApplication.class)
public class EmailClientImplTest {
...
}
And also add the needed properties/configs in
/src/test/resources/application.yml
Good luck!
I figured it out, I am using Mockito and used that to annotate my test class. This allowed me to get a mock of the service class that i am trying to use.
#RunWith(MockitoJUnitRunner.class)
public class SmartHomeControllerTest {..
#Mock
private DeviceService deviceService;
}
Try with #InjectMock instead of #Mock
You should run your test with spring boot runner