We have some APIs we use in our application that are not accessible from local developer machines due to firewalls.
I want to use mockServer to mock some of these API so we can develop locally.
When running tests mockServer can be started and stopped using the maven build phases process-test-classes and verify respectively.
How can I get it to run when I start the application with mvn spring-boot:run ?
It's possible to override beans on springboot.
So you can use your beans and switch for mock values as you need
The example bellow is overriding services and using mock as you prefer but you can use interfaces as well.
Creating a service
#Service
public class ServiceReal {
#Autowired(required = false) // must be required=false. May be disabled by using mock configuration
private JdbcTemplate jdbcTemplate;
public String getInfo() {
return jdbcTemplate...// get a real value from database
}
}
Creating a mock service
#Service
#Primary
#Profile("mocklocal")
public class ServiceMock extend ServiceReal {
#Override
public String getInfo() {
return "Mocked value"
}
}
Config beans to choose one of them on properties later
#Profile("mocklocal")
#PropertySource("classpath:application-mocklocal.properties")
#Configuration
public class ConfigMock {
private static final String PROP_VALUE_TRUE = "true";
private static final boolean PROP_FALSE_DEFAULT_MISSING = false;
private static final String PROP_SERVICE_REAL = "mocklocal.service.real";
private static final String PROP_SERVICE2_REAL = "mocklocal.service2.real";
#Bean
#ConditionalOnProperty( value = PROP_SERVICE_REAL, havingValue = PROP_VALUE_TRUE, matchIfMissing = PROP_FALSE_DEFAULT_MISSING)
public ServiceReal serviceReal(){
return new ServiceMock();
}
#Bean
#ConditionalOnProperty( value = PROP_SERVICE2_REAL, havingValue = PROP_VALUE_TRUE, matchIfMissing = PROP_FALSE_DEFAULT_MISSING)
public Service2Real service2Real(){
return new Service2Mock();
}
}
Config your application-mocklocal.properties to use mock
# using ConfigMock
spring.profiles.active=mocklocal
# settig spring to override service and use mock
spring.main.allow-bean-definition-overriding=true
# disable some configuration not required in mocks. you can adjust for amqp, database or other configuration
spring.autoconfigure.exclude[0]=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
spring.autoconfigure.exclude[1]=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
spring.autoconfigure.exclude[2]=org.springframework.boot.autoconfigure.orm.jpa.DataSourceTransactionManagerAutoConfiguration
# enable your service to use mocks not real services
mocklocal.service.real=true
mocklocal.service2.real=true
so if you start your app using --spring.profiles.active=mocklocal you will got mock values
And you can use on tests as well
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
#SpringBootTest
#TestPropertySource(locations = "classpath:application-mocklocal.properties")
public class RunIntegrationTests {
#Autowired
private MockMvc mockMvc;
#Test
public void run() throws Exception{
...
}
}
When running tests mockServer can be started and stopped using the maven build phases process-test-classes and verify respectively.
So there must be some (pom) configuration like:
<plugin>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-maven-plugin</artifactId>
<version>3.10.8</version>
<configuration>
<serverPort>1080</serverPort>
<proxyPort>1090</proxyPort>
<logLevel>DEBUG</logLevel>
<initializationClass>org.mockserver.maven.ExampleInitializationClass</initializationClass>
</configuration>
<executions>
<execution>
<id>process-test-classes</id>
<phase>process-test-classes</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>verify</id>
<phase>verify</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
This would start a mock server at process-test-classes (so before test phase) and stop it at validate (so after (post-)integration-test phase).
(link1, link2)
How can I get it to run when I start the application with mvn spring-boot:run ?
To run it with mvn spring-boot:run:
Just run mvn mockserver:start spring-boot:run! (pack it into a script/IDE launch..) (recommended)
Implement custom plugin, which commbines spring-boot-maven and mockserver-maven-plugin... (and then run mvn com.example:custom-plugin:run)
.
I had created a MockServer for my team once, for quite a similar purpose here (fortunately a short demo is also available). You can set up this server independently (say on a localhost) and add the request (url and payloads) with the corresponding response json you want to this server.
The one time change you need to do inside your project will be to route all your API request to this Mockserver during development/testing, which can be done by changing the base url of all the APIs you will be using and setting up the mockserver with appropriate json request and response. It can be done as simple as this:
public class BaseUrlLoader {
public static String NEWSRIVER_BASE_URL;
public static String FACEBOOK_BASE_URL;
public static String TWITTER_BASE_URL;
private static final String MOCKSERVER_BASE_URL = "mocksrvr.herokuapp.com/TEAM-SECRET-KEY";
public static void load(){
Properties properties= new Properties();
String activeProfile;
try{
properties.load(ClassLoader.getSystemResourceAsStream("application.properties"));
} catch (IOException e) {
System.out.println("Not able to load the application.properties file");
return;
}
activeProfile = properties.getProperty("spring.profiles.active");
System.out.println("Using "+activeProfile);
if(activeProfile.equals("Test")){
NEWSRIVER_BASE_URL=MOCKSERVER_BASE_URL;
FACEBOOK_BASE_URL= MOCKSERVER_BASE_URL;
TWITTER_BASE_URL= MOCKSERVER_BASE_URL;
}else{
NEWSRIVER_BASE_URL="api.newsriver.io";
FACEBOOK_BASE_URL="api.facebook.com";
TWITTER_BASE_URL="api.twitter.com";
}
System.out.println(NEWSRIVER_BASE_URL);
}
}
// Example- Use APIs as
public class NewsFetch {
...
public NewsFetch(){ BaseUrlLoader.load(); }
private URI buildURL(APIQuery apiQuery) throws URISyntaxException {
String mainURL = BaseUrlLoader.NEWSRIVER_BASE_URL+"v2/search";
URIBuilder url = new URIBuilder(mainURL);
url.addParameter("query", apiQuery.getLuceneQuery());
url.addParameter("soryBy", apiQuery.getSortBy());
url.addParameter("sortOrder", apiQuery.getSortOrder());
url.addParameter("limit", apiQuery.getLimit());
return url.build();
}
public HttpResponse <String> fetch(APIQuery apiQuery) throws URISyntaxException, IOException, InterruptedException {
URI uri = buildURL(apiQuery);
HttpRequest request = HttpRequest.newBuilder()
.GET()
.header("Authorization", KEY)
.uri(uri)
.build();
...
}
}
// and add the request like http://mocksrvr.herokuapp.com/TEAM-SECRET-KEY/v2/search/... to the Mockserver with the response you want.
The baseurl will change according to the current active profile. This mockserver is simple and can even be integrated with the Slackbot. See more in the readme file. There can be many bugs in the project and contributions will be appreciated.
Related
My db properties are kept in application-test.properties (I am running Springboot application in test profile) and the Datasource is referred through #Autowired annotation. It throws NullPointerException when I try to use datasource.getConnection().
I have referred similar questions and mostly all of them include some solutions with bean xml configurations. In my case I am not explicitly using any bean configurations. Every datasource properties are kept in application-test.properties file and I am referring through it using Datasource. I am a newbie to Springboot and any help would be great.
My repository class
#Repository
public class ActualUserDetailsDAO {
#Autowired
DataSource dataSource;
public String getPriorityType(String idNo) throws Exception {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
String cxPriorityType = null;
int count = 0;
try {
con = dataSource.getConnection();
String sql = ConfigurationHandler.getInstance().getConfigValue("sample.query");
......................
} catch (SQLException e) {
................
} catch (Exception e) {
..............
} finally {
.................
}
return cxPriorityType;
}
My application properties
spring.main.banner-mode=off
server.port=8180
# Datasource settings
spring.datasource.initialize=true
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.name=camst2
spring.datasource.url=jdbc:oracle:thin:#..................
spring.datasource.username=username
spring.datasource.password=password
# Tomcat JDBC settings
spring.datasource.tomcat.initial-size=10
spring.datasource.tomcat.max-active=100
spring.datasource.tomcat.min-idle=10
spring.datasource.tomcat.max-idle=100
#spring.datasource.tomcat.max-wait=6000
spring.datasource.tomcat.max-wait=30000
#spring.datasource.tomcat.test-on-connect=true
#spring.datasource.tomcat.test-on-borrow=true
#spring.datasource.tomcat.test-on-return=true
# Tomcat AccessLog
server.tomcat.accesslog.suffix=.log
server.tomcat.accesslog.prefix=access_log
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.directory=/tomcat/logs
server.tomcat.accesslog.pattern=%h %l %u %t %r %s %b %D
My application class
#SpringBootApplication
public class Application {
#Autowired
DataSource dataSource;
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
I found the solution. The problem was in my controller class. I was creating an instance of the my repository class by myself. I should have used #Autowired instead.
#RestController
public class ActualUserDetails implements ActualUserDetailsInt {
#RequestMapping(value = "/foo", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getActualUserDetails(#PathVariable("idNo") String idNo, #RequestParam("lob") String lob,
#RequestParam("offerSellingType") String offerSellingType) {
//do something
ActualUserDetailsDAO actualUserDetailsDAO = new ActualUserDetailsDAO();
actualUserDetailsDAO.getPriorityType(idNo);
//do something
I changed this into following.
#RestController
public class ActualUserDetails implements ActualUserDetailsInt {
#Autowired
ActualUserDetailsDAO actualUserDetailsDAO;
#RequestMapping(value = "/foo", method = RequestMethod.GET, produces =
MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getActualUserDetails(#PathVariable("idNo") String idNo,
#RequestParam("lob") String lob,
#RequestParam("offerSellingType") String offerSellingType) {
//do something
actualUserDetailsDAO.getPriorityType(idNo);
//do something
Manually creating object of my repository class did not detected dataSource defined inside it. Autowiring my repository class in my controller class seems to solve this problem.
If your data source is not been detected for any reason, I strongly recommend to have a deeper look on your code.
Following are some of the things to look for when this kind of error happens.
Look for the correct folder structure (application properties file
reside under resources folder)
If you are running Spring in a different profile (say test
profile), make sure relevant configurations are written in
application-test.properties
Check for proper annotation in relevant classes
Make sure your application properties are not overridden by any other
configurations
I have a Spring Boot application that hosts a REST API.
Depending on which files get deployed, I want to be able to have it load additional controllers from what is essentially a "plugin" JAR file.
For example, I'd love to be able to do something like this:
java -jar myapp.jar -Dplugins.directory=/opt/myapp/plugins
Is this possible?
Note: these would not be loaded on the fly; once deployed, the set of plugins will remain fixed. I want one application jar that remains the same in every deployment, and the behavior of the application will be determined by the plugins that are deployed alongside it.
it may not 100% Satisfy your demand.
I have two suggestion.
the easy one.
java -jar stackoverflow-1.0-SNAPSHOT.jar --spring.profiles.active=prod
and put different value "#Profile" on your controller.
#RestController
#Profile("prod")
public class URLOneController {
#PostMapping(value = "/url", consumes="application/json", produces="application/json")
public ResponseEntity<HttpStatus> insertClaim(#RequestBody String messageBody) {
return new ResponseEntity<>(HttpStatus.OK);
}
}
second suggestion ,dynamic load beanDefiniton.
#Configuration
#ConditionalOnProperty(name="external.controller.enable",havingValue = "true")
public class ExternalClassDefinitionProcessor implements
BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
Class<?> aClass = null;
try {
aClass = contextClassLoader.loadClass("com.jin.learn.demo.UrlOneController");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(aClass);
beanDefinitionBuilder.addPropertyReference("personDao", "personDao");
BeanDefinition personManagerBeanDefinition = beanDefinitionBuilder
.getRawBeanDefinition();
registry.registerBeanDefinition("UrlOneController",
personManagerBeanDefinition);
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory) throws BeansException {
}
}
package your controller into normal jar(not use spring-boot-maven-plugin )
run your app like this command line
java -Dloader.path="lib/,config/,/home/jin/Desktop/abc/target/abc-1.0-SNAPSHOT.jar" -jar stackoverflow-1.0-SNAPSHOT.jar --external.controller.enable=true
the extra contorller in abc-1.0-SNAPSHOT.jar and your main app is stackoverflow-1.0-SNAPSHOT.jar
tips:
stackoverflow-1.0-SNAPSHOT.jar should package zip format .
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>ZIP</layout>
</configuration>
</plugin>
I have a multi-module project with two projects: backend and client. The backend is a normal Spring Boot Rest API, nothing special. The client module is just a Java Library using the Rest API.
The backend has packaging of "war" as the backend as it uses JSPs, too and needs to be deployed to a servlet container. The backend is still easily testable with #SpringBootTest.
Now I want to have some integration tests inside the client module using the backend module as a sandbox server.
To use all the backend classes in the client module I added
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<attachClasses>true</attachClasses>
</configuration>
</plugin>
and configured the backend as a test dependency in client with classes
In my client/src/test/java I have a helper class which starts up the backend module
#Configuration
public class SandboxServer {
#Bean
public ConfigurableApplicationContext backend() {
return
new SpringApplicationBuilder(BackendApplication.class)
.sources(SandboxServerConfig.class)
.run("spring.profiles.active=sandbox")
}
}
The profile "sandbox" is used to setup a test database etc. But I had more problems. First problem was regarding the document root, so I configured it:
public class SandboxServerConfig
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.setDocumentRoot(new File("../backend/src/main/webapp"));
}
}
But it still does not work as Spring is not picking up backend/src/main/resources/application.properties
That might be correct as it is not in the root classpath of the client module.
So it does not really work. I guess it is not possible to just start up the sibling module in an Integration test.
How can I achieve to start up the sibling spring boot module for integration testing? What is the best practice for szenarios like this?
You can override the application.properties location using TestPropertySource like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = BlaApplication.class)
#TestPropertySource(locations="/path/to/backend/src/main/resources/application.properties")
public class ExampleApplicationTests {
}
I found a much more solid solution. In my sibling Project "frontend" I have a Component which is starting up the backend server in integration mode if and only if it is not already running.
Benefits:
The real WAR is tested
You can start the WAR before in your IDE and let the tests run fast
If you run it with maven it is started up before all tests only once
No build configuration needed (like pre-integration in maven)
process is seperated from Junit runtime so no hassle with complex setups.
Drawbacks:
You need to build the package before you can run any integration test in the frontend. But hey, you should build your package before you test it. That's what integration test is all about.
And here is my SandboxServerProcess.class.
import org.springframework.stereotype.Component;
import javax.annotation.*;
import javax.annotation.*;
import java.io.*;
import java.net.*;
import java.util.*;
#Component
#Profile("integration")
public class SandboxServerProcess {
private static final String WAR = "../backend/target/backend.war";
private final static int PORT = 8081;
private boolean startedByMe;
#PostConstruct
public void start() throws Exception {
if (isStarted()) {
return;
}
testWarExists();
packagedWar("start");
if (waitForStartup()) {
startedByMe = true;
return;
}
throw new RuntimeException("Sandbox Server not started");
}
private void testWarExists() {
File file = new File(WAR);
if (!file.exists()) {
throw new RuntimeException("WAR does not exist:" + file.getAbsolutePath());
}
}
#PreDestroy
public void stop() throws IOException {
if (startedByMe) {
packagedWar("stop");
}
}
private void packagedWar(String command) throws IOException {
ProcessBuilder builder = new ProcessBuilder();
builder.environment().put("MODE", "service");
builder.environment().put("SPRING_PROFILES_ACTIVE", "integration");
builder.environment().put("APP_NAME", "backend");
builder.environment().put("PID_FOLDER", "./");
builder.environment().put("LOG_FOLDER", "./");
List<String> commands = new ArrayList<>();
commands.add(WAR);
commands.add(command);
builder.command(commands);
builder.inheritIO();
builder.redirectErrorStream(true);
builder.start();
}
private boolean isStarted() {
try {
Socket socket = new Socket();
InetSocketAddress sa = new InetSocketAddress("localhost", PORT);
socket.connect(sa, 500);
logger.warn("SandboxServer is started");
return true;
} catch (IOException e) {
return false;
}
}
private boolean waitForStartup() throws InterruptedException {
for (int i = 1; i < 30; i++) {
if (isStarted()) {
return true;
}
logger.warn("SandboxServer not yet ready, tries: " + i);
Thread.sleep(1000);
}
return false;
}
}
I am trying to serve a static file in camel routes.
The routes in my main class contains this piece of code:
public final void configure() throws Exception {
// declaring camel routes
// match on uri prefix must be true when parameters are passed as part of the uri
// for example, "http://localhost/hello/rick"
// http.port is in local.properties file user-api
from("jetty:http://0.0.0.0:{{http.port}}/user/dist/?matchOnUriPrefix=true")
.process( new StaticProcessor( "help", "index.html", "dist"))
.routeId( "static");
from("jetty:http://0.0.0.0:{{http.port}}/user?matchOnUriPrefix=true")
.to("cxfbean:userService");
}
This works good. When I hit the url: http://xxxx:8086/user/dist/index.html, my index page is rendered and the url shows to behttp://xxxx:8086/user/dist/ in url bar.
But when I reload the page (press F5), the url becomes: http://xxxx:8086/user/dist// and I get error like:
This page should have been replaced by Swagger. Do you have the
following in your application's pom.xml as the only reference to the
swagger-maven-plugin?
<build>
<plugins>
<plugin>
<groupId>com.github.kongchen</groupId>
<artifactId>swagger-maven-plugin</artifactId>
<executions>
<execution>
<id>swagger</id>
<phase>compile</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
I have this dependency in my effective POM. So what am I missing?
I wish to achieve that any url with http://clv035sl-8947d6:8888/user/dist should route the call to index.html. Why I need to explictly write index.html at end of the url?
Any help/ suggestion will be appreciated.
I made a simple JUnit Test case to test your scenario based on this blog post.
Where are the implementation of the StaticProcessor class? I've implemented something for this scenario that is quite similar (IMHO):
public void configure() throws Exception {
from("jetty:http://0.0.0.0:8080/user/dist?matchOnUriPrefix=true").process(new Processor() {
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
String relativepath = in.getHeader(Exchange.HTTP_PATH, String.class);
String requestPath = in.getHeader("CamelServletContextPath", String.class); //CamelServletContextPath
if (relativepath.isEmpty() || relativepath.equals("/")) {
relativepath = "index.html";
}
final String formattedPath = String.format("%s/%s", requestPath, relativepath);
InputStream pathStream = this.getClass().getResourceAsStream(formattedPath);
Path path = FileSystems.getDefault().getPath(this.getClass().getResource(formattedPath).getPath());
Message out = exchange.getOut();
try {
out.setBody(IOUtils.toByteArray(pathStream));
out.setHeader(Exchange.CONTENT_TYPE, Files.probeContentType(path));
} catch (IOException e) {
out.setBody(relativepath + " not found.");
out.setHeader(Exchange.HTTP_RESPONSE_CODE, "404");
}
}
}).routeId("static");
}
It takes from the classpath the resources that needed to be exposed and set the out message to the response. Please, take a look at the entire test case.
I've tested the following URLs:
http://localhost:8080/user/dist/
http://localhost:8080/user/dist
http://localhost:8080/user/dist/index.html
Please note that I added the swagger plugin dependency just like you did.
Let me know if that helps or point where the StaticProcessor implementation is that I may test with it and edit my answer.
Cheers
I made a test case to test my Rest Web Service. but in test case I am seeing that the request is going to default port of jersey test framework which is http://localhost:9998 whereas my service is register on http://localhost:8080. I am unable to find that how can I change its port to 8080
public class UMServiceTest extends JerseyTest {
#Override
public Application configure() {
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
return new ResourceConfig(UMService.class);
}
#Test
public void testFetchAll() {
System.out.println(getBaseUri()+"==========");
Response output = target("usermanagement").path("um").path("user").request().get();
assertEquals("should return status 200", 200, output.getStatus());
//assertNotNull("Should return list", output.getEntity());
}
you can give a command line arguments when you run the test such as,
Maven
mvn yourpack.UMServiceTest -Djersey.config.test.container.port=8080
or in eclipse you can pass this in run config 'Arguments' tab
You could change the Systemproperty from TestProperties for the JerseyTest too.
public class UMServiceTest extends JerseyTest
{
static
{
System.setProperty("jersey.config.test.container.port", "0");
}
...
In addition to kuhajeyan's answer here's maven config for JerseyTest port:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
....
<systemProperties>
<property>
<name>jersey.config.test.container.port</name>
<value>4410</value>
</property>
</systemProperties>
</configuration>
</plugin>
As of Jersey 2.33, the configure method can be set as in the following example taken from the Jersey Docs
#Override
protected Application configure() {
// Find first available port.
forceSet(TestProperties.CONTAINER_PORT, "0");
return new ResourceConfig(Resource.class);
}
In case, the configureDeployment is used to specify the resource, the below approach can be used.
#BeforeClass
public static void beforeClass() {
System.setProperty(
// Use a random available port
"jersey.config.test.container.port", String.valueOf(SocketUtils.findAvailableTcpPort()));
}
#AfterClass
public static void afterClass() {
System.clearProperty("jersey.config.test.container.port");
}
#Override
protected DeploymentContext configureDeployment() {
return ServletDeploymentContext.forServlet(
new ServletContainer(ResourceConfig.forApplicationClass(TestApplication.class)))
.contextPath("/cas/api")
.build();
}
Source: https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/test-framework.html#parallel