I'm trying to make a mockMvc call run together with wiremock. But the mockMvc call below in the code keeps throwing 404 instead of the expected 200 HTTP status code.
I know that wiremock is running .. I can do http://localhost:8070/lala through browser when wiremock is running.
Can someone please advise ?
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApp.class)
#AutoConfigureMockMvc
public class MyControllerTest {
#Inject
public MockMvc mockMvc;
#ClassRule
public static final WireMockClassRule wireMockRule = new WireMockClassRule(8070);
#Rule
public WireMockClassRule instanceRule = wireMockRule;
public ResponseDefinitionBuilder responseBuilder(HttpStatus httpStatus) {
return aResponse()
.withStatus(httpStatus.value());
}
#Test
public void testOne() throws Exception {
stubFor(WireMock
.request(HttpMethod.GET.name(), urlPathMatching("/lala"))
.willReturn(responseBuilder(HttpStatus.OK)));
Thread.sleep(1000000);
mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.GET, "/lala")) .andExpect(status().isOk());
}
}
By default #SpringBootTest runs with a mocked environment and hence doesn't have a port assigned, docs
Another useful approach is to not start the server at all but to test only the layer below that, where Spring handles the incoming HTTP request and hands it off to your controller. That way, almost of the full stack is used, and your code will be called in exactly the same way as if it were processing a real HTTP request but without the cost of starting the server
so MockMvc does not pointing to wiremockport (8070) which exactly says 404. If you want do test with wiremock you can use HttpClients like here
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet("http://localhost:8070/lala");
HttpResponse httpResponse = httpClient.execute(request);
Or you can just use spring boot web integration test feature by mocking any service calling from controller like shown here
Related
I'm trying to deal with Rest api tests.
Well, the controller in my code connects to the external api parsing and returns in the form of JSON.
I'm trying to run a test so that it returns the result of my application’s logic.
Unfortunately, during testing I can't connect with api github.
java.lang.AssertionError: Status expected:<200> but was:<404>
Expected :200
Actual :404
#WebMvcTest(RepositoryDetailsController.class)
#ContextConfiguration(classes = RepositoryDetailsController.class)
class RepositoryDetailsControllerTestt {
#Autowired
private MockMvc mvc;
#MockBean
private RepositoryDetailsService service;
#InjectMocks
private RepositoryDetailsController repositoryDetailsController;
#Before
public void setUp() {
mvc = MockMvcBuilders.standaloneSetup(repositoryDetailsController).build();
}
#Test
public void mockTest() throws Exception {
RepositoryDetailsResponse details = new RepositoryDetailsResponse();
details.setDescription("Ruby toolkit for the GitHub API");
details.set"https://github.com/octokit/octokit.rb.git");
details.setStars("57892");
details.setName("octokit/octokit.rb");
mvc.perform(get("repository/{owner}/{repository}","octokit","octokit.rb")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.*", hasSize(5)))
.andExpect(jsonPath("$.fullName").value(details.getName()))
.andExpect(jsonPath("$.description").value(details.getDescription()))
.andExpect(jsonPath("$.cloneUrl").value(details.getUrl()))
.andExpect(jsonPath("$.stars").value(details.getStars()));
}
If you can avoid it you usually don't want to connect to an external API in your tests. Having an external API test dependency makes life harder e.g. what if the API is down or what if the API had a breaking change?
There are few tools that you can use to mock this external API, hardcoding the expected responses while still making the request:
Wiremock - very generic, exposes a mock server that can be used with any library that makes the API call
Spring's MockRestServiceServer - especially useful if your code is using RestTemplate to make the API call
Test Containers - if you have a Docker image that provides a mock API
In your example it looks like you attempted to use MockMvc instead of MockRestServiceServer but there is not enough code to tell what you are doing exactly.
In my current project we are working on microservices(web app).
In unit tests we try to cover 85-90% of our code. I have noticed 2 approaches of testing using spring:
Inject a controller and invoke its methods directly
Form a proper request where you can specify cookies, headers... and then make a call
Moreover, we won't be able to test authentication with the 1 approach.
Which of the next spring testing ways should be used? And what are the (dis-)advantages of each type?
#RestController
class MyController {
#PostMapping(path="/path")
public String handle(#RequestBody MyRequest request) {
//service invoked
return "some value";
}
}
JUnit approach #1
#LotsOfAnnotations
class ControllerTest1 {
#Autowired
private MyController myController;
#Test
public String verboseNameTest() {
// Mock 3rd party calls
....
// Form request
MyRequest request = new MyRequest();
// Invoke testing method
myController.handle(request);
// Assert
}
}
JUnit approach #2
#LotsOfAnnotations
class ControllerTest2 {
#Autowired
private TestRestTemplate testTemplate;
private MockRestServiceServer server;
#Autowired
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webAppContext;
#Before
public void setup() {
server = MockRestServiceServer.createServer(testTemplate.getRestTemplate());
mockMvc = MockMvcBuilders.webAppContextSetup(this.webAppContext).build();
}
#Test
public String verboseNameTest() {
// Mock 3rd party calls
....
// Form request
String jsonStringRequest = "{}";
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/path")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonStringRequest);
// Make a call
MvcResult result = this.mockMvc
.perform(requestBuilder)
.andExpect(status().isOk())
.andReturn();
// Assert
}
}
As far as I can see you need to understand when to mock the service and when to make a direct call.
In your first approach you are directly calling the web service so let's say for example you have a huge code base and multiple developers are working at a time so you would get constant updates in the classes and if you want to test your piece of code(which is why you use JUnit) then you making direct call can result in error/ failed test case because someone might have changed something.
In second approach or mocking basically removes that possibility by mocking the other service which your code would be needing so that will make you code test easier.
But there are people who questions what's the use of mocking when you are not even testing the whole thing which is true up to certain point but the main reason for mocking is to test your piece of code only irrespective of other services that code is dependent on. Also if you talk about making actual service call that should be part of integration testing or end to end testing.
A Spring Boot application provides a REST API. It has several containers and is unit-tested. I have modified this application to add a new controller with a new PUT route, to upload a file.
When I run this application with mvn spring-boot:run, I can access the new route via Postman. However, in my unit tests, I cannot.
My unit test autowires a WebApplicationContext into the test and creates a MockMVC from it, like so:
#SpringBootTest
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
class NewControllerTest {
#Autowired
private WebApplicationContext ctx;
// ... more autowirings ...
private MockMvc mockMvc;
#Before
public void setUp() {
mockMvc = standaloneSetup(ctx).build();
}
// ... actual test cases ...
}
Then, within the test case, I use the route like this:
mockMvc.perform(put("/newctrl/route/" + param + "/img.jpg")
.contentType("application/octet-stream").content(data))
.andExpect(status().isOk());
where data is a byte[] array and param is an integer generated at run time.
The API is accesed nearly the same way in several other test cases. The only difference is that in the other test cases, the request method is never PUT, and the content is always a string. All other test cases work.
My new test, however, produces an error message:
java.lang.AssertionError: Status expected:<200> but was:<404>
In the log, I find:
2016-11-27 20:01:22.263 WARN 15648 --- [ main] o.s.web.servlet.PageNotFound : No mapping found for HTTP request with URI [/newctrl/route/42/img.jpg] in DispatcherServlet with name ''
I use mvn spring-boot:run and copy-paste the URL from the log entry to Postman. I attach an image and, voilá, it works.
Why, then, does my test not work?
EDIT: Excerpts from NewController:
#RequestMapping("/newctrl")
#RestController
#RequiredArgsConstructor(onConstructor = #__({#Autowired}))
public class NewController {
#RequestMapping(value = "/route/{param}/{filename:.+}")
public void theRoute(#PathVariable Long param, #PathVariable String filename,
HttpServletRequest request, HttpServletResponse response) throws IOException {
// ...
}
}
When working with Spring Boot to build micro-services its very easy to write extensive and very readable integration tests and mock remote service requests with MockRestServiceServer.
Is there a way to use similar approach to perform additional integration test on ZuulProxy? What I would like to achieve is being able to mock remote servers that ZuulProxy would forward to and validate that all of my ZuulFitlers behaved as expected. However, ZuulProxy is using RestClient from Netflix (deprecated it would seem?) which naturally does not use RestTemplate which could be re-configured by MockRestServiceServer and I currently can't find a good way of mocking responses from remote services for proxied requests.
I have a micro-service that is responsible for handling API Session Key creation and then will act similar to an API Gateway. Forwarding is done with Zuul Proxy to underlying exposed services, and Zuul Filters will detect if Session key is valid or not. An integration test would therefore create a valid session and then forward to a fake endpoint, e.g 'integration/test'.
Specifying that 'integration/test' is a new endpoint is possible by setting a configuration property on #WebIntegrationTest, I can successfully mock all services that are being handled via RestTemplate but not Zuul forwarding.
What's the best way to do achieve mocking of a forward target service?
Check out WireMock. I have been using it to do integration level testing of my Spring Cloud Zuul project.
import static com.github.tomakehurst.wiremock.client.WireMock.*;
public class TestClass {
#Rule
public WireMockRule serviceA = new WireMockRule(WireMockConfiguration.options().dynamicPort());
#Before
public void before() {
serviceA.stubFor(get(urlPathEqualTo("/test-path/test")).willReturn(aResponse()
.withHeader("Content-Type", "application/json").withStatus(200).withBody("serviceA:test-path")));
}
#Test
public void testRoute() {
ResponseEntity<String> responseEntity = this.restTemplate.getForEntity("/test-path/test", String.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
serviceA.verify(1, getRequestedFor(urlPathEqualTo("/test-path/test")));
}
}
The accepted answer has the main idea. But I struggle on some points until figure out the problem. So I would like to show a more complete answer using also Wiremock.
The test:
#ActiveProfiles("test")
#TestPropertySource(locations = "classpath:/application-test.yml")
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWireMock(port = 5001)
public class ZuulRoutesTest {
#LocalServerPort
private int port;
private TestRestTemplate restTemplate = new TestRestTemplate();
#Before
public void before() {
stubFor(get(urlPathEqualTo("/1/orders/")).willReturn(aResponse()
.withHeader("Content-Type", MediaType.TEXT_HTML_VALUE)
.withStatus(HttpStatus.OK.value())));
}
#Test
public void urlOrders() {
ResponseEntity<String> result = this.restTemplate.getForEntity("http://localhost:"+this.port +"/api/orders/", String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
verify(1, getRequestedFor(urlPathMatching("/1/.*")));
}
}
And the application-test.yml:
zuul:
prefix: /api
routes:
orders:
url: http://localhost:5001/1/
cards:
url: http://localhost:5001/2/
This should work.
But Wiremock has some limitations for me. If you has proxy requests with different hostnames running on different ports, like this:
zuul:
prefix: /api
routes:
orders:
url: http://lp-order-service:5001/
cards:
url: http://lp-card-service:5002/
A localhost Wiremock running on the same port will no be able to help you. I'm still trying to find a similar Integration Test where I could just mock a Bean from Spring and read what url the Zuul Proxy choose to route before it make the request call.
We have a Spring application, building with Gradle, running with Spring Boot 1.2.5.RELEASE. We wrote some initial integration tests using using Rest Assured to test against our REST endpoints. This worked, and our application's REST endpoints were responding appropriately via the browser and Postman.
Then we used Spring Security to implement a OncePerRequestFilter and our own AuthenticationProvider. Our authentication is working fine, and the browser and Postman are still receiving appropriate responses, however our integration tests no longer work.
Stepping through a test, we do see our Controller endpoints being called and returning the correct output, but beyond this point we receive error (with a null stacktrack) of org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.AbstractMethodError.
We've made progress by initializing our integration tests with Spring Security, we've tried abandoning Rest Assured and just using MockMvc, we've tried going back to Rest Assured and initializing with MockMvc. No luck so far.
Our initialization code is below, the commented out portions are for Rest Assured, the current implementation is directly using MockMvc.
Any help would be greatly appreciated!
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = IamExtensionApplication.class)
#WebIntegrationTest
public class OurIntegrationTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
// #Autowired
// private FilterChainProxy filterChainProxy;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).
apply(springSecurity()).
// addFilters(filterChainProxy).
build();
// RestAssuredMockMvc.mockMvc(mockMvc);
}
#Test
public void our_test() {
try {
ResultActions resp = amockMvc.perform(post("/our/endpoint").param("test", "test_value"));
} catch (Exception e) {
e.printStackTrace();
}
// MockMvcResponse resp = given()
// .param("test", "test_value")
// .when()
// .post("/our/endpoint");
}
We tried several variations of these configurations, but the one we finally tried that worked (that we didn't actually find documented anywhere) was to pass our filterChainProxy as a parameter to the springSecurity() function.
Hope this helps others!