I have the Spring application.
We have the service which makes HTTP requests to external services. Now I think about writing Unit Test on this functionality. I want to write the integration tests. Thus I want to know that service request is correct.
Is there ways to do it in spring? (actually I don't know ways to do it outside the Spring too)
In tests you can use some kind of mock server. Wiremock for example.
Wiremock can work as a proxy and log all your requests and responses. More than that - it can then save responses and use them as stubs. It can be very useful to test integration problems with number of fault simulations.
For spring boot there is a starter:
<dependency>
<groupId>com.epages</groupId>
<artifactId>wiremock-spring-boot-starter</artifactId>
<version>0.7.18</version>
<scope>test</scope>
</dependency>
More documentation available here: https://github.com/ePages-de/restdocs-wiremock
Then create a test and setup a proxy:
#RunWith(SpringRunner.class)
#EnableAutoConfiguration
#SpringBootTest(
classes = {
YourApplication.class,
}
)
#WireMockTest // it comes from starter
public class YourTest {
#Inject
private WireMockServer server;
#Value("http://localhost:${wiremock.port}")
private String uri;
#Test
public void shouldLogAllTheThings() throws Exception {
server.stubFor(get(urlMatching("/other/service/.*"))
.willReturn(
aResponse()
.proxiedFrom("http://otherhost.com/approot")
));
// your call with rest template using uri field value
// as base uri
}
#TestConfiguration
public static class Internal {
#Inject
public WireMockConfiguration configuration;
#PostConstruct
public void wiremock() {
// this adds verbosive logger
// which will print all communication
configuration.notifier(new Slf4jNotifier(true));
}
}
}
More info:
http://wiremock.org/docs/proxying/
Mappings can be saved using this doc: http://wiremock.org/docs/record-playback/
If you don't want to use starter, you can use wiremock as JUnit rule:
http://wiremock.org/docs/junit-rule/
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 19 days ago.
Improve this question
I have Spring Security config that configures OAuth2 resource server and endpoints with the predefined scopes and what you can do with what endpoint. Config is below:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(
auth -> auth
.antMatchers("/api/v1/private/**")
.hasAnyAuthority("SCOPE_api:read", "SCOPE_api:write")
.antMatchers("/api/v1/public/**")
.authenticated()
).oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.httpBasic().disable()
.csrf().disable()
.formLogin().disable()
.logout().disable();
return http.build();
}
}
There are multiple answers on the similar question on StackOverflow such as 1, 2, 3, even though I look at them, I cannot wrap my head around it and how to test the setup, correctness etc.
My questions has two parts:
How can I test my config? There is a nice Baeldung article but it forces to run 2 separate applications to integration tests to succeed. Great for demo, not so great for CI and development.
What is normally can be tested in these use-cases? What is normal to unit-test and what is normal to integration test?
I have solved similar task with testcontainers and Keycloack, but that was possible as we had in test and prod Keycloack. This time, I have no control over resource server and I don't know it's type, so it makes my head to explode.
As with all 3rd parties, you shouldn't test a 3rd party service (at least not from your Java project). You'll just have to trust that the devs are doing their job, and that the service correctly issues a JWT.
What you want to test are OAuth scopes (aka Authorities in Spring Security). That is done by mocking your Authentication context so that it includes a principal with your required scopes.
The way I set it up in my projects is the following:
Make sure you have org.springframework.security:spring-security-test as a dependency
In your test source, create an annotation that will act as your fake user setup
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithMockUserSecurityContextFactory.class)
public #interface WithMockUser {
long userId() default 1L;
String[] authorities() default "ROLE_USER";
}
Again in your test source make a class that will mock the Security Context
public class WithMockUserSecurityContextFactory implements WithSecurityContextFactory<WithMockUser> {
#Override
public SecurityContext createSecurityContext(WithMockUser annotation) {
var context = SecurityContextHolder.createEmptyContext();
var authorities = Arrays.stream(annotation.authorities())
.map(SimpleGrantedAuthority::new)
.toList();
var principal = UserPrincipal.builder()
.userId( annotation.userId() )
.email("test#test.com")
.authorities( authorities )
.enabled(true)
.build();
var auth = new UserPrincipalAuthentication(principal);
context.setAuthentication(auth);
return context;
}
}
Note that UserPrincipalAuthentication is my custom class that extends AbstractAuthenticationToken.
Now you should be able to annotate your test methods with the custom annotation and pass in the authorities you want to test for, such as this:
#AutoConfigureMockMvc
#SpringBootTest
class EndpointTest {
#Test
#WithMockUser(authorities = "scope:READ")
void returnsUserData_ifHasReadScope() {
// ...
// MockMvc calls to your endpoint, and assertions
}
}
What this does, is it completely removes the 3rd party resource server from testing scope. In our test scope we don't care which server the JWT came from, it doesn't matter how it was decoded (that can be done through other unit tests), all that matters is if Access Control is working, given that User comes with a particular set of Authorities.
For end-to-end or "smoke" tests (test which include more than just your resource-server: authorization-server, REST client, etc.), their are better suited tools than Java tests, and yes, this is very likely to require you to lift containers and program robots to manipulate UIs (perform user login, fill forms, etc.).
As stated by #Thorne in his answer, testing JWT decoding and validation should not be in the scope of your Java tests (unless you write your own decoder). Also exchange with an authorization-server to issue valid access-tokens would be way to slow and fragile for a decent coverage in unit-tests (JUnit with #ExtendWith(SpringExtension.class), #WebMvcTest or #WebfluxTest) or spring-boot integration-tests (#SpringBootTest).
What I unit and integration test regarding security conf is access-control, using mocked OAuth2 identities.
spring-security-test comes with some MockMvc request post-processors (see org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt in your case) as well as WebTestClient mutators (see org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockJwt) to configure Authentication of the right type (JwtAuthenticationToken in your case) and set it in test security context, but this is limited to MockMvc and WebTestClient and as so to #Controller tests.
Sample usage with your security conf and a #GetMapping("/api/v1/private/machin") #PreAuthorize("hasAuthority('SCOPE_api:read')"):
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
#WebMvcTest(controllers = MachinController.class, properties = { "server.ssl.enabled=false" })
class MachinControllerTest {
#Autowired
MockMvc api;
#Test
void givenUserIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/private/machin"))
.andExpect(status().isUnauthorized());
}
#Test
void givenUserIsGrantedWithApiRead_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/private/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(new SimpleGrantedAuthority("SCOPE_api:read"))))))
.andExpect(status().isOk());
}
#Test
void givenUserIsAuthenticatedButNotGrantedWithApiRead_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/private/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(new SimpleGrantedAuthority("SCOPE_openid"))))))
.andExpect(status().isForbidden());
}
}
You might also use #WithMockJwtAuth from this libs I maintain. This repo contains quite a few samples for unit and integration testing of any kind of #Component (#Controllers of course but also #Services or #Repositories decorated with method-security).
Above Sample becomes:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-oauth2-test</artifactId>
<version>6.0.12</version>
<scope>test</scope>
</dependency>
#WebMvcTest(controllers = MachinController.class, properties = { "server.ssl.enabled=false" })
class MachinControllerTest {
#Autowired
MockMvc api;
#Test
void givenUserIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/private/machin"))
.andExpect(status().isUnauthorized());
}
#Test
#WithMockJwtAuth("SCOPE_api:read")
void givenUserIsGrantedWithApiRead_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/private/machin"))
.andExpect(status().isOk());
}
#Test
#WithMockJwtAuth("SCOPE_openid")
void givenUserIsAuthenticatedButNotGrantedWithApiRead_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/private/machin"))
.andExpect(status().isForbidden());
}
}
Spring-addons starter
In the same repo, you'll find starters to simplify your resource server security config (and also synchronize sessions and CSRF protection disabling as the second should not be disabled with active sessions...).
Usage is super simple and all you'd have to change between environments would be properties, even if using different OIDC authorization-servers, for instance a standalone Keycloak on your dev machine and a cloud provider like Cognito, Auth0, etc. in production.
Instead of directly importing spring-boot-starter-oauth2-resource-server, import a thin wrapper around it (composed of 3 files only):
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.12</version>
</dependency>
As you already apply fine grained method-security to protected routes, you could probably empty this Java conf (keep default expressionInterceptUrlRegistryPostProcessor bean which requires users to be authenticated to access any route but those listed in com.c4-soft.springaddons.security.permit-all property):
#Configuration
#EnableMethodSecurity
public class SecurityConfig {
#Bean
ExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() {
return (AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) -> registry
.requestMatchers("/api/v1/private/**").hasAnyAuthority("SCOPE_api:read", "SCOPE_api:write")
.anyRequest().authenticated();
}
}
Replace spring.security.oauth2.resourceserver properties with:
# Single OIDC JWT issuer but you can add as many as you like
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/master
# Mimic spring-security default authorities converter: map from "scope" claim (accepts a comma separated list claims), with "SCOPE_" prefix
com.c4-soft.springaddons.security.issuers[0].authorities.claims=scope
com.c4-soft.springaddons.security.issuers[0].authorities.prefix=SCOPE_
# Fine-grained CORS configuration can be set per path as follow:
com.c4-soft.springaddons.security.cors[0].path=/api/**
com.c4-soft.springaddons.security.cors[0].allowed-origins=https://localhost,https://localhost:8100,https://localhost:4200
com.c4-soft.springaddons.security.cors[0].allowedOrigins=*
com.c4-soft.springaddons.security.cors[0].allowedMethods=*
com.c4-soft.springaddons.security.cors[0].allowedHeaders=*
com.c4-soft.springaddons.security.cors[0].exposedHeaders=*
# Comma separated list of ant path matchers for resources accessible to anonymous
com.c4-soft.springaddons.security.permit-all=/api/v1/public/**
I'm working on a Spring-Boot web application. The usual way of writing integration tests is:
#Test
#Transactional
#Rollback(true)
public void myTest() {
// ...
}
This works nicely as long as only one thread does the work. #Rollback cannot work if there are multiple threads.
However, when testing a #RestController class using the Spring REST templates, there are always multiple threads (by design):
the test thread which acts as the client and runs the REST template
the server thread which receives and processes the request
So you can't use #Rollback in a REST test. The question is: what do you use instead to make tests repeatable and have them play nicely in a test suite?
#DirtiesContext works, but is a bad option because restarting the Spring application context after each REST test method makes the suite really slow to execute; each test takes a couple of milliseconds to run, but restarting the context takes several seconds.
First of all, testing a controller using a Spring context is no unit test. You should consider writing a unit test for the controller by using mocks for the dependencies and creating a standalone mock MVC:
public class MyControllerTest {
#InjectMocks
private MyController tested;
// add #Mock annotated members for all dependencies used by the controller here
private MockMvc mvc;
// add your tests here using mvc.perform()
#Test
public void getHealthReturnsStatusAsJson() throws Exception {
mvc.perform(get("/health"))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status", is("OK")));
}
#Before
public void createControllerWithMocks() {
MockitoAnnotations.initMocks(this);
MockMvcBuilders.standaloneSetup(controller).build()
}
}
This even works if you use an external #ControllerAdvice for error handling etc by simply calling setControllerAdvice() on the MVC builder.
Such a test has no problems running in parallel and is much faster by no need to setup a Spring context at all.
The partial integration test you described is also useful to make sure the right wiring is used and all tested units work together as expected. But I would more go for a more general integration test including multiple/all endpoints checking if they work in general (not checking the edge cases) and mocking only services reaching out to external (like internal REST clients, replacing the database by one in memory, ...). With this setup you start with a fresh database and maybe will not even need to rollback any transaction. This is of course most comfortable using a database migration framework like Liquibase which would setup your in memory db on the fly.
Below is my implement followed by #4
private MockMvc mockMvc;
#Mock
private LoginService loginService;
#Mock
private PersonalService personalService;
#InjectMocks
private LoginController loginController;
#BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(loginController).build();
}
#Test
void simple_login() throws Exception {
Mockito.when(loginService.login(Mockito.anyString(), Mockito.anyString()))
.thenReturn(UserAccessData.builder()
.accessToken("access_token_content")
.refreshToken("refresh_token_count")
.build());
mockMvc.perform(
MockMvcRequestBuilders.post("/login/simple")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.param("accountName", "1234561")
.param("password", "e10adc3949ba59abbe56e057f20f883e")
)
.andDo(print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(jsonPath("$.tokens.accessToken", is("access_token_content")))
.andExpect(jsonPath("$.tokens.refreshToken", is("refresh_token_count")))
;
}
recently I changed my spring boot properties to define a management port.
In doing so, my unit tests started to fail :(
I wrote a unit test that tested the /metrics endpoint as follows:
#RunWith (SpringRunner.class)
#DirtiesContext
#SpringBootTest
public class MetricsTest {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
/**
* Called before each test.
*/
#Before
public void setUp() {
this.context.getBean(MetricsEndpoint.class).setEnabled(true);
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
/**
* Test for home page.
*
* #throws Exception On failure.
*/
#Test
public void home()
throws Exception {
this.mvc.perform(MockMvcRequestBuilders.get("/metrics"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
Previously this was passing. After adding:
management.port=9001
The tests started failing with:
home Failed: java.lang.AssertionError: Status expected: <200> but was: <404>
I tried changing the #SpringBootTest annotation with:
#SpringBootTest (properties = {"management.port=<server.port>"})
Where is the number used for the server.port. This didn't seem to make any difference.
So then changed the management.port value in the property file to be the same as the server.port. Same result.
The only way to get the test to work is remove the management.port from the property file.
Any suggestions/thoughts ?
Thanks
For Spring Boot 2.x the integration tests configuration could be simplified.
For example simple custom heartbeat endpoint
#Component
#Endpoint(id = "heartbeat")
public class HeartbeatEndpoint {
#ReadOperation
public String heartbeat() {
return "";
}
}
Where integration test for this endpoint
#SpringBootTest(
classes = HeartbeatEndpointTest.Config.class,
properties = {
"management.endpoint.heartbeat.enabled=true",
"management.endpoints.web.exposure.include=heartbeat"
})
#AutoConfigureMockMvc
#EnableAutoConfiguration
class HeartbeatEndpointTest {
private static final String ENDPOINT_PATH = "/actuator/heartbeat";
#Autowired
private MockMvc mockMvc;
#Test
void testHeartbeat() throws Exception {
mockMvc
.perform(get(ENDPOINT_PATH))
.andExpect(status().isOk())
.andExpect(content().string(""));
}
#Configuration
#Import(ProcessorTestConfig.class)
static class Config {
#Bean
public HeartbeatEndpoint heartbeatEndpoint() {
return new HeartbeatEndpoint();
}
}
}
For Spring boot test we need to specify the port it needs to connect to.
By default, it connects to server.port which in case of actuators is different.
This can be done by
#SpringBootTest(properties = "server.port=8090")
in application.properties we specify the management port as below
...
management.server.port=8090
...
Did you try adding the following annotation to your test class?
#TestPropertySource(properties = {"management.port=0"})
Check the following link for reference.
Isn't there an error in the property name?
Shouldn't be
#TestPropertySource(properties = {"management.server.port=..."}) instead of #TestPropertySource(properties = {"management.port=.."})
The guide stated that this can be achieved with #AutoConfigureMetrics.
And I moved with this.
Regardless of your classpath, meter registries, except the in-memory backed, are not auto-configured when using #SpringBootTest.
If you need to export metrics to a different backend as part of an integration test, annotate it with #AutoConfigureMetrics.
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.metrics
Had the same issue, you just have to make the management.port null by adding this in your application-test.properties (set it to empty value)
management.port=
Make sure you use the test profile in your JUnit by annotating the class with
#ActiveProfiles("test")
Try using
#SpringBootTest(properties = {"management.port="})
Properties defined in the #SpringBootTest annotation have a higher precedence than those in application properties. "management.port=" will "unset" the management.port property.
This way you don't have to worry about configuring the port in your tests.
I was facing the same issue and tried several things but this is how I was able to solve mine without making any change in the application.yaml
Sample actuator endpoint
#Component
#RestControllerEndpoint(id = "endpoint")
public class SampleEndpoint
{
#GetMapping
public String sampleEndpoint(){
return ""
}
}
Unit test case
#RunWith(SpringRunner.class)
#SpringBootTest(
classes = {SampleEndpointTest.Config.class},
properties = {"management.server.port="}
)
#AutoConfigureMockMvc
public class SampleEndpointTest
{
#Autowired
private MockMvc mockMvc;
#SpringBootApplication(scanBasePackageClasses = {SampleEndpoint.class})
public static class Config
{
}
#Test
public void testSampleEndpoint() throws Exception
{
mockMvc.perform(
MockMvcRequestBuilders.get("/actuator/enpoint").accept(APPLICATION_JSON)
).andExpect(status().isOk());
}
Since now info endpoint must be enabled manually make sure the SpringBootTest tag includes this in properties, like this:
#SpringBootTest(
properties = {
"management.info.env.enabled=true" ,
"management.endpoints.web.exposure.include=info, health"
})
I had this problem recently, and as none of the above answers made any sense to me, I decided to do a bit more reading. In my case, I had already defined both server.port and management.server.port as 8091 in my test application-test.yaml file, and could not understand why my test was getting a connection refused error message.
It turns out that instead of using the annotation #SpringBootTest() I needed to use #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) - which causes the port numbers in the yaml file to be used. This is briefly discussed in the manual. Quoting the relevant section:
DEFINED_PORT — Loads an EmbeddedWebApplicationContext and provides a real servlet environment. Embedded servlet containers are started and listening on a defined port (i.e from your application.properties or on the default port 8080).
It seems in SpringBootTest the default is to avoid starting a real servlet environment, and if no WebEnvironment is explicitly specified then SpringBootTest.WebEnvironment.MOCK is used as a default.
After a long search: There is this nice Springboot annotation called #LocalManagementPort!
It works similar to #LocalServerPort but for actuator endpoins.
An example config would look as follows
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MetricsIT {
#Autowired
RestTemplateBuilder restTemplateBuilder;
#LocalManagementPort
int managementPort;
#Test
public void testMetrics(){
ResponseEntity<String> response = restTemplateBuilder
.rootUri("http://localhost:" + managementPort + "/actuator")
.build().exchange("/metrics", HttpMethod.GET, new HttpEntity<>(null), String.class);
}
}
I have written a Restful Web service and have to test it using JUnit4. I have already written a Client using Jersey Client. But want to know if I can test my service only with junit4. Can someone help me with sample at least.
My rest service has authenticate method that takes user name, password and returns a token.
I have written test case for authenticate method. But I am not sure how to test using url.
public class TestAuthenticate {
Service service = new Service();
String username = "user";
String password = "password";
String token;
#Test(expected = Exception.class)
public final void testAuthenticateInputs() {
password = "pass";
service.authenticate(username, password);
}
#Test(expected = Exception.class)
public final void testAuthenticateException(){
username = null;
String token = service.authenticate(username, password);
assertNotNull(token);
}
#Test
public final void testAuthenticateResult() {
String token = service.authenticate(username, password);
assertNotNull(token);
}
}
If you want to test using the URL, then you will need to start a server from your test. You can explicitly start an embedded server, which is pretty common for tests. Something like
public class MyResourceTest {
public static final String BASE_URI = "http://localhost:8080/api/";
private HttpServer server;
#Before
public void setUp() throws Exception {
final ResourceConfig rc = new ResourceConfig(Service.class);
server = GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
}
#After
public void tearDown() throws Exception {
server.stop();
}
#Test
public void testService() {
Client client = ClientBuilder.newClient();
WebTarget target = client.target(BASE_URI).path("service");
...
}
}
It's basically an integration test. You're starting the Grizzly container and loading a ResourceConfig to the server with only the Service class. Of course you could add more classes to the configuration. You can use "real" resource config if you wanted.
The above test uses this dependency
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
<version>${jersey2.version}</version>
</dependency>
Another option, which is the one I prefer, is to make use of the Jersey Test Framework, which will start an embedded container for you. A test might look something more like
public class SimpleTest extends JerseyTest {
#Override
protected Application configure() {
return new ResourceConfig(Service.class);
}
#Test
public void test() {
String hello = target("service").request().get(String.class);
}
}
Using this dependency
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
And embedded Grizzly container will get started under the hood, with your ResourceConfig configuration. In both examples above it is assumed the #Path value for the Service class is service, as you can see in the test URLs.
Some Resources
Jersey 2 Test Framework user guide
Some Examples
How to write Unit Test for this class using Jersey 2 test framework
How to in-memory unit test Spring-Jersey
Example with Mockito, Test Framework, and Jersey 2
Example with Mockito, Test Framework, and Jersey 1
UPDATE
If you're not using Maven, here are the jars you will need to run an embedded Grizzly container for the Jersey Test Fraemwork
I usually search for all my jars here. You can select the version and there should be a link in the next page, to download. You can use the search bar to search for the others.
Here's a simple running example, once you have all the jars
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.core.DefaultResourceConfig;
import com.sun.jersey.spi.container.servlet.WebComponent;
import com.sun.jersey.test.framework.JerseyTest;
import com.sun.jersey.test.framework.WebAppDescriptor;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import junit.framework.Assert;
import org.junit.Test;
public class SimpleTest extends JerseyTest {
#Path("service")
public static class Service {
#GET
public String getTest() { return "Hello World!"; }
}
public static class AppConfig extends DefaultResourceConfig {
public AppConfig() {
super(Service.class);
}
}
#Override
public WebAppDescriptor configure() {
return new WebAppDescriptor.Builder()
.initParam(WebComponent.RESOURCE_CONFIG_CLASS,
AppConfig.class.getName())
.build();
}
#Test
public void doTest() {
WebResource resource = resource().path("service");
String result = resource.get(String.class);
Assert.assertEquals("Hello World!", result);
System.out.println(result);
}
}
You're most likely not going to have the resources and ResourceConfig in the same class as the test, but I just want to keep it simple and all visible in one class.
Whether you are using a web.xml or a ResourceConfig subclass (as shown above), you can cut down what you test by using a separate ResourceConfig, built in the test class, as I have done. Otherwise, if you are using your normal ResourceConfig class, you can just replace it in the configure method.
The configure method, is pretty much just building a web.xml file, just in Java code. You can see different methods in the WebAppDescriptor.Builder, like initParam, which is the same as an <init-param> in your web xml. You can simply use the string in the arguments, but there are some constants, as I used above.
The #Test is you usual JUnit test that will run. It is using the Jersey Client. But instead of creating the Client, you can simply just use the preconfigured Client by just accessing the resource() method, which returns a WebResource. If you are familiar with the Jersey Client, then this class should not be new to you.
Take a look at Alchemy rest client generator. This can generate a proxy implementation for your JAX-RS webservice class using jersey client behind the scene. Effectively you will call you webservice methods as simple java methods from your unit tests. Handles http authentication as well.
There is no code generation involved if you need to simply run tests so it is convenient.
The demo here setup up grizzly and uses the generator above to run junit tests.
Disclaimer: I am the author of this library.
I think #peeskillet has given you the needed prerequisites, i.e you need to run your web-service in an embedded web server. You could also look into dropwizard or spring-boot support for doing this conveniently.
As for actually verifying the response I would keep it simple and go with JUnit & http-matchers (see https://github.com/valid4j/http-matchers)
Assume I have made a simple client in my application that uses a remote web service that is exposing a RESTful API at some URI /foo/bar/{baz}. Now I wish to unit test my client that makes calls to this web service.
Ideally, in my tests, I’d like to mock the responses I get from the web service, given a specific request like /foo/bar/123 or /foo/bar/42. My client assumes the API is actually running somewhere, so I need a local "web service" to start running on http://localhost:9090/foo/bar for my tests.
I want my unit tests to be self-contained, similar to testing Spring controllers with the Spring MVC Test framework.
Some pseudo-code for a simple client, fetching numbers from the remote API:
// Initialization logic involving setting up mocking of remote API at
// http://localhost:9090/foo/bar
#Autowired
NumberClient numberClient // calls the API at http://localhost:9090/foo/bar
#Test
public void getNumber42() {
onRequest(mockAPI.get("/foo/bar/42")).thenRespond("{ \"number\" : 42 }");
assertEquals(42, numberClient.getNumber(42));
}
// ..
What are my alternatives using Spring?
Best method is to use WireMock.
Add the following dependencies:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-core</artifactId>
<version>4.0.6</version>
</dependency>
Define and use the wiremock as shown below
#Rule
public WireMockRule wireMockRule = new WireMockRule(8089);
String response ="Hello world";
StubMapping responseValid = stubFor(get(urlEqualTo(url)).withHeader("Content-Type", equalTo("application/json"))
.willReturn(aResponse().withStatus(200)
.withHeader("Content-Type", "application/json").withBody(response)));
If you use Spring RestTemplate you can use MockRestServiceServer. An example can be found here REST Client Testing With MockRestServiceServer.
If you want to unit test your client, then you'd mock out the services that are making the REST API calls, i.e. with mockito - I assume you do have a service that is making those API calls for you, right?
If on the other hand you want to "mock out" the rest APIs in that there is some sort of server giving you responses, which would be more in line of integration testing, you could try one of the many framework out there like restito, rest-driver or betamax.
You can easily use Mockito to mock a REST API in Spring Boot.
Put a stubbed controller in your test tree:
#RestController
public class OtherApiHooks {
#PostMapping("/v1/something/{myUUID}")
public ResponseEntity<Void> handlePost(#PathVariable("myUUID") UUID myUUID ) {
assert (false); // this function is meant to be mocked, not called
return new ResponseEntity<Void>(HttpStatus.NOT_IMPLEMENTED);
}
}
Your client will need to call the API on localhost when running tests. This could be configured in src/test/resources/application.properties. If the test is using RANDOM_PORT, your client under test will need to find that value. This is a bit tricky, but the issue is addressed here: Spring Boot - How to get the running port
Configure your test class to use a WebEnvironment (a running server) and now your test can use Mockito in the standard way, returning ResponseEntity objects as needed:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestsWithMockedRestDependencies {
#MockBean private OtherApiHooks otherApiHooks;
#Test public void test1() {
Mockito.doReturn(new ResponseEntity<Void>(HttpStatus.ACCEPTED))
.when(otherApiHooks).handlePost(any());
clientFunctionUnderTest(UUID.randomUUID()); // calls REST API internally
Mockito.verify(otherApiHooks).handlePost(eq(id));
}
}
You can also use this for end-to-end testing of your entire microservice in an environment with the mock created above. One way to do this is to inject TestRestTemplate into your test class, and use that to call your REST API in place of clientFunctionUnderTest from the example.
#Autowired private TestRestTemplate restTemplate;
#LocalServerPort private int localPort; // you're gonna need this too
How this works
Because OtherApiHooks is a #RestController in the test tree, Spring Boot will automatically establish the specified REST service when running the SpringBootTest.WebEnvironment.
Mockito is used here to mock the controller class -- not the service as a whole. Therefore, there will be some server-side processing managed by Spring Boot before the mock is hit. This may include such things as deserializing (and validating) the path UUID shown in the example.
From what I can tell, this approach is robust for parallel test runs with IntelliJ and Maven.
What you are looking for is the support for Client-side REST Tests in the Spring MVC Test Framework.
Assuming your NumberClient uses Spring's RestTemplate, this aforementioned support is the way to go!
Hope this helps,
Sam
Here is a basic example on how to mock a Controller class with Mockito:
The Controller class:
#RestController
#RequestMapping("/users")
public class UsersController {
#Autowired
private UserService userService;
public Page<UserCollectionItemDto> getUsers(Pageable pageable) {
Page<UserProfile> page = userService.getAllUsers(pageable);
List<UserCollectionItemDto> items = mapper.asUserCollectionItems(page.getContent());
return new PageImpl<UserCollectionItemDto>(items, pageable, page.getTotalElements());
}
}
Configure the beans:
#Configuration
public class UserConfig {
#Bean
public UsersController usersController() {
return new UsersController();
}
#Bean
public UserService userService() {
return Mockito.mock(UserService.class);
}
}
The UserCollectionItemDto is a simple POJO and it represents what the API consumer sends to the server. The UserProfile is the main object used in the service layer (by the UserService class). This behaviour also implements the DTO pattern.
Finally, mockup the expected behaviour:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
#Import(UserConfig.class)
public class UsersControllerTest {
#Autowired
private UsersController usersController;
#Autowired
private UserService userService;
#Test
public void getAllUsers() {
initGetAllUsersRules();
PageRequest pageable = new PageRequest(0, 10);
Page<UserDto> page = usersController.getUsers(pageable);
assertTrue(page.getNumberOfElements() == 1);
}
private void initGetAllUsersRules() {
Page<UserProfile> page = initPage();
when(userService.getAllUsers(any(Pageable.class))).thenReturn(page);
}
private Page<UserProfile> initPage() {
PageRequest pageRequest = new PageRequest(0, 10);
PageImpl<UserProfile> page = new PageImpl<>(getUsersList(), pageRequest, 1);
return page;
}
private List<UserProfile> getUsersList() {
UserProfile userProfile = new UserProfile();
List<UserProfile> userProfiles = new ArrayList<>();
userProfiles.add(userProfile);
return userProfiles;
}
}
The idea is to use the pure Controller bean and mockup its members. In this example, we mocked the UserService.getUsers() object to contain a user and then validated whether the Controller would return the right number of users.
With the same logic you can test the Service and other levels of your application. This example uses the Controller-Service-Repository Pattern as well :)