I'm using SpringBoot 2 and Spring 5 (RC1) to expose reactive REST services. but I can't manage to write unit test for those controllers.
Here is my controller
#Api
#RestController
#RequestMapping("/")
public class MyController {
#Autowired
private MyService myService;
#RequestMapping(path = "/", method = RequestMethod.GET)
public Flux<MyModel> getPages(#RequestParam(value = "id", required = false) String id,
#RequestParam(value = "name", required = false) String name) throws Exception {
return myService.getMyModels(id, name);
}
}
myService is calling a database so I would like not to call the real one. (I don't wan't integration testing)
Edit :
I found a way that could match my need but I can't make it work :
#Before
public void setup() {
client = WebTestClient.bindToController(MyController.class).build();
}
#Test
public void getPages() throws Exception {
client.get().uri("/").exchange().expectStatus().isOk();
}
But I'm getting 404, seems it can't find my controller
You have to pass actual controller instance to bindToController method.
As you want to test mock environment, you'll need to mock your dependencies, for example using Mockito.
public class MyControllerReactiveTest {
private WebTestClient client;
#Before
public void setup() {
client = WebTestClient
.bindToController(new MyController(new MyService()))
.build();
}
#Test
public void getPages() throws Exception {
client.get()
.uri("/")
.exchange()
.expectStatus().isOk();
}
}
More test examples you can find here.
Also, I suggest switching to constructor-based DI.
Related
#DeleteMapping(value = "/{id}", produces = APPLICATION_JSON_VALUE)
public ResponseEntity<Void> delete(#PathVariable Long id) {
log.debug("Delete by id Logo : {}", id);
try {
Logo entr = new Logo();
entr.setId(id);
logoRepository.delete(entr);
return ResponseEntity.ok().build();
} catch (Exception x) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}
For testing controllers we could mock mvc. Inject mock of service layers inside the controller.
Since you have mock for your service you could mock the response that service should return for a transaction using Mockito.when statement.
A sample code will be like.
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = HelloController.class)
public class HelloWorldTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private HelloService service;
#Test
public void testShouldReturnMessage() throws Exception {
when(service.sayHello()).thenReturn("Hello World");
mockMvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.ALL))
.andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(MockMvcResultMatchers.content().string("Hello World"))
.andExpect(MockMvcResultMatchers.header().string("Content-Type", "text/plain;charset=UTF-8"))
.andExpect(MockMvcResultMatchers.header().string("Content-Length", "11"));
}
}
Reference - https://www.nexsoftsys.com/articles/spring-boot-controller-unit-testing.html#:~:text=Unit%20testing%20Spring%20Boot%20controllers%20Technology%20Unit%20testing,is%20to%20validate%20each%20unit%20performs%20as%20designed.
We are calling controller through web test client. I have 2 external api calls in my service class. Now i want to write the test cases for these scenarios.
Please suggest me how to mock those external service calls using mock web server.
I tried like below but its calling actual external calls.
#RunWith(SpringRunner.class)
#WebFluxTest
#ContextConfiguration(classes = { TestConfig.class })
public class FunctionalTest {
#Autowired
public MockWebServer mockWebServer;
#Autowired
private WebTestClient webTestClient;
#Autowired
private ApplicationContext context;
#Before
public void setUp() {
this.webTestClient = WebTestClient.bindToApplicationContext(this.context).configureClient() //
.baseUrl("http://localhost:2020")
.exchangeStrategies(ExchangeStrategies.builder().codecs((configurer) -> {
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper));
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper));
}).build()).build();
MockitoAnnotations.initMocks(this);
}
#Test
public void testMyInfo() throws Exception {
MockResponse businessResponse = new MockResponse().setResponseCode(200)
.setHeader(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE).setBody(getRequestDataFromFile("business.json"));
MockResponse cheapResponse = new MockResponse().setResponseCode(200)
.setHeader(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE).setBody(getRequestDataFromFile("cheap.json"));
mockWebServer.setDispatcher(new Dispatcher() {
#Override
public MockResponse dispatch(RecordedRequest request) {
if (request.getPath().endsWith("/business"))
return businessResponse;
if (request.getPath().endsWith("/cheap")) {
return cheapResponse;
}
return cheapResponse;
}
});
testResponse(webTestClient);
}
public void testResponse(final WebTestClient webTestClient) {
((WebTestClient.RequestBodySpec)
webTestClient.get().uri("/webflux")).exchange().expectStatus().isEqualTo(200)
.expectBody(Data[].class).consumeWith(result -> {
System.out.println("result........." + result);
});
}
I would like to create Test for my rest controller:
#Controller
#RequestMapping("/v2/api/show/project")
public class ApiAccessController {
private final ApiAccessService apiAccessService;
#Autowired
ApiAccessController(ApiAccessService apiAccessService){
this.apiAccessService = apiAccessService;
}
#PutMapping(value = "/{id}/apikey")
public ResponseEntity<ApiKeyResponse> generateApiKey(#PathVariable("id")Long id, Principal principal) {
return apiAccessService.generateApiKey(id, principal.getName());
}
}
My test looks as follow:
#RunWith(SpringJUnit4ClassRunner.class)
public class ApiAccessControllerTest {
private MockMvc mockMvc;
Principal principal = new Principal() {
#Override
public String getName() {
return "TEST_PRINCIPAL";
}
};
#InjectMocks
ApiAccessController apiAccessController;
#Mock
ProjectRepository projectRepository;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.standaloneSetup(apiAccessController).build();
}
#Test
public void testGenerateApiKey() throws Exception {
Project project = new Project();
project.setId((long) 1);
project.setName("test");
project.setDescription("testdesc");
project.setCiid("ciid");
when(projectRepository.save(any(Project.class))).thenReturn(project);
mockMvc.perform(MockMvcRequestBuilders.put("/v2/api/show/project/" + project.getId() +"/apikey").principal(principal))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
Which is ment to create project and then run generateApiKey on this project, however I get NullpointerException looking like mocked controller cannot find created entity
could anyone please point me in the right direction as I am just starting with testing?
You should mock ApiAccessService instead of ProjectRepository.
Have a look at the code:
#RunWith(SpringJUnit4ClassRunner.class)
public class ApiAccessControllerTest {
private MockMvc mockMvc;
private Principal principal = () -> "TEST_PRINCIPAL";
#InjectMocks
private ApiAccessController apiAccessController;
#Mock
private ApiAccessService apiAccessService;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(apiAccessController).build();
}
#Test
public void testGenerateApiKey() throws Exception {
long id = 1L;
when(apiAccessService.generateApiKey(id, principal.getName())).thenReturn(new ApiKeyResponse(111L));
mockMvc.perform(MockMvcRequestBuilders.put("/v2/api/show/project/{id}/apikey", id).principal(principal))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
If you want to create integration test, that tests ApiAccessController -> ApiAccessService -> ProjectRepository integration you need to load your context (use for example #SpringBootTest).
Also you need to fix controller, use ResponseEntity.ok(...) :
#PutMapping(value = "/{id}/apikey")
public ResponseEntity<ApiKeyResponse> generateApiKey(#PathVariable("id") Long id, Principal principal) {
return ResponseEntity.ok(apiAccessService.generateApiKey(id, principal.getName()));
}
You can find really good examples of all test types in this repository MVC tests examples
The Mock you are creating is not referenced in the Controller. The Service you reference in the Controller is not part of your test setup. Therefore any access to the Service will cause a NullPointerException as the Service is not set.
I'm new to Zuul J-unit testing. I have a couple of filters which is ChangeRequestEntityFilter and SessionFilter, Where I pasted my filtercode below. Can someone tell me how to write a Junit for the filter. I've searched and trying to use MockWire for the unit testing(Also I pasted my empty methods with basic annotations and WireMock port). I need at-least one proper example how this J-unit for Zuul works. I've referred the http://wiremock.org/docs/getting-started/ doc. Where I got what to do, but not how to do.
public class ChangeRequestEntityFilter extends ZuulFilter {
#Autowired
private UtilityHelperBean utilityHelperBean;
#Override
public boolean shouldFilter() {
// //avoid http GET request since it does'nt have any request body
return utilityHelperBean.isValidContentBody();
}
#Override
public int filterOrder() {
//given priority
}
#Override
public String filterType() {
// Pre
}
#Override
public Object run() {
RequestContext context = getCurrentContext();
try {
/** get values profile details from session */
Map<String, Object> profileMap = utilityHelperBean.getValuesFromSession(context,
CommonConstant.PROFILE.value());
if (profileMap != null) {
/** get new attributes need to add to the actual origin microservice request payload */
Map<String, Object> profileAttributeMap = utilityHelperBean.getProfileForRequest(context, profileMap);
/** add the new attributes in to the current request payload */
context.setRequest(new CustomHttpServletRequestWrapper(context.getRequest(), profileAttributeMap));
}
} catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(new IllegalStateException("ChangeRequestEntityFilter : ", ex));
}
return null;
}
}
I know ,I'm asking more. But give me any simple working complete example, I'm fine with it.
My current code with basic annotations and WireMock port.
#RunWith(SpringRunner.class)
#SpringBootTest
#DirtiesContext
#EnableZuulProxy
public class ChangeRequestEntityFilterTest {
#Rule
public WireMockRule wireMockRule = new WireMockRule(8080);
#Mock
ChangeRequestEntityFilter requestEntityFilter;
int port = wireMockRule.port();
#Test
public void changeRequestTest() {
}
}
Have you tried #MockBean?
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
"When #MockBean is used on a field, as well as being registered in the application context, the mock will also be injected into the field. Typical usage might be:"
#RunWith(SpringRunner.class)
public class ExampleTests {
#MockBean
private ExampleService service;
#Autowired
private UserOfService userOfService;
#Test
public void testUserOfService() {
given(this.service.greet()).willReturn("Hello");
String actual = this.userOfService.makeUse();
assertEquals("Was: Hello", actual);
}
#Configuration
#Import(UserOfService.class) // A #Component injected with ExampleService
static class Config {
}
}
Here there is another approach:
private ZuulPostFilter zuulPostFilter;
#Mock
private anotherService anotherService;
#Mock
private HttpServletRequest request;
#Before
public void before() {
MockitoAnnotations.initMocks(this);
MonitoringHelper.initMocks();
zuulPostFilter = new ZuulPostFilter(anotherService);
doNothing().when(anotherService).saveInformation(null, false);
}
#Test
public void postFilterTest() {
log.info("postFilterTest");
RequestContext context = new RequestContext();
context.setResponseDataStream(new ByteArrayInputStream("Test Stream".getBytes()));
context.setResponseGZipped(false);
RequestContext.testSetCurrentContext(context);
when(request.getScheme()).thenReturn("HTTP");
RequestContext.getCurrentContext().setRequest(request);
ZuulFilterResult result = zuulPostFilter.runFilter();
assertEquals(ExecutionStatus.SUCCESS, result.getStatus());
assertEquals("post", zuulPostFilter.filterType());
assertEquals(10, zuulPostFilter.filterOrder());
}
In this case you can test the filter and mock the services inside it without having to autowire it, the problem with the #autowired is that if you have services inside the filter, then it is going to be an integration test that is going to be more difficult to implement.
Im currently working with Camel's mock component and i would like to test it on an existing routes. Basically i want to retain the existing routes defined in the app, but inject a few mocks during test, to verify or at least peek on the current exchange contents.
Based on the docs and from the Apache Camel Cookbook. I've tried to use #MockEndpoints
Here's the route builder
#Component
public class MockedRouteStub extends RouteBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(MockedRouteStub.class);
#Override
public void configure() throws Exception {
from("direct:stub")
.choice()
.when().simple("${body} contains 'Camel'")
.setHeader("verified").constant(true)
.to("direct:foo")
.otherwise()
.to("direct:bar")
.end();
from("direct:foo")
.process(e -> LOGGER.info("foo {}", e.getIn().getBody()));
from("direct:bar")
.process(e -> LOGGER.info("bar {}", e.getIn().getBody()));
}
}
Here's my test (currently its a springboot project):
#RunWith(SpringRunner.class)
#SpringBootTest
#MockEndpoints
public class MockedRouteStubTest {
#Autowired
private ProducerTemplate producerTemplate;
#EndpointInject(uri = "mock:direct:foo")
private MockEndpoint mockCamel;
#Test
public void test() throws InterruptedException {
String body = "Camel";
mockCamel.expectedMessageCount(1);
producerTemplate.sendBody("direct:stub", body);
mockCamel.assertIsSatisfied();
}
}
message count is 0 and it looks more like #MockEndpoints is not triggered.
Also, logs indicate that the log is triggered
route.MockedRouteStub : foo Camel
An alternative i've tried is to use an advice:
...
#Autowired
private CamelContext context;
#Before
public void setup() throws Exception {
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
mockEndpoints();
}
});
}
The startup logs indicate that advice is in place:
c.i.InterceptSendToMockEndpointStrategy : Adviced endpoint [direct://stub] with mock endpoint [mock:direct:stub]
But still my test fails with the message count = 0.
Posting the answer which worked for the setup that i have.
Without any changes to the RouteBuilder, the Test would look something like this:
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest
#MockEndpoints
public class MockedRouteStubTest {
#Autowired
private ProducerTemplate producerTemplate;
#EndpointInject(uri = "mock:direct:foo")
private MockEndpoint mockCamel;
#Test
public void test() throws InterruptedException {
String body = "Camel";
mockCamel.expectedMessageCount(1);
producerTemplate.sendBody("direct:stub", body);
mockCamel.assertIsSatisfied();
}
}