I am learning spring-boot by reading tutorials on the official website http://spring.io. When I tried to implement a service discovery client, I got this error information:Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.. I read lots of solutions on Google but none of them helped. Can you give me some advice? I am new to spring-boot.
Here is my code:
- The spring-boot application class
#EnableDiscoveryClient
#SpringBootApplication
public class DiscoveryClientApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(DiscoveryClient.class, args);
}
}
The controller class:
#RestController
public class Controller {
#Autowired
private DiscoveryClient discoveryClient;
#RequestMapping("service-instances/{appName}")
public List<ServiceInstance> serviceInstanceByApplicationName(#PathVariable
String applicationName) {
return this.discoveryClient.getInstances(applicationName);
}
}
And the application.properties
server.port = 20000
eureka.instance.prefer-ip-address = true
eureka.client.register-with-eureka = true
eureka.client.fetch-registry = true
eureka.client.service-url.default-zone = http://localhost:8761/eureka
spring.application.name = service-discovery-client
spring.jpa.hibernate.ddl-auto = create
spring.datasource-url = jdbc:mysql://localhost:3306/spring-test
spring.datasource.username = root
spring.datasource.password = 80966cc9
spring.jpa.database = MYSQL
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL8Dialect
My project is a maven project and the discovery client is a sub-project, it's pom.xml is inherited from the father project. In the father project, I added all dependencies. The two other sub-projects run normally, only this service can't run.
Related
During my unit tests using Spock and Testcontainers, the JpaRepository is not functioning properly and is not being wired correctly. This issue persists even in non-integration tests.
As suggested in another discussion, I attempted to resolve the issue by adding the spock-spring dependency to my pom.xml file. It didn't work.
No matter the scenario, the repository consistently returns as null in all instances.
An example:
#Testcontainers
class PostgresTestContainer extends Specification {
#Autowired
private PersonRepository personRepository
#Shared
PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:12-alpine")
.withDatabaseName("test")
.withUsername("test")
.withPassword("test")
def "waits until postgres accepts jdbc connections"() {
when: "querying the database"
def response = personRepository.findAll()
then: "result is returned"
response == 0
}
}
The database is being initialized using PostgresContainer and Testcontainers annotation. However your test infrastructure doesn't know about the database. If using spring boot, then few things are missed
Add SpringBootTest or DataJpaTest annotation on top of the class. This way the spring application context is created with the right classes and PersonRepository will be injected
Switch to
static postgresContainer = new PostgreSQLContainer("postgres:12-alpine")
#Shared
PostgreSQLContainer <?> cassandra = cassandraContainer
In order to make use of the database provided by Testcontainers, add
#DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgresContainer::getJdbcUrl);
registry.add("spring.datasource.username", postgresContainer::getUsername);
registry.add("spring.datasource.password", postgresContainer::getPassword);
}
I would suggest to separate the container setup like the following:
PostgresEnvrionement
#Testcontainers
public class PostgresEnvironment {
#Container
public static PostgreSQLContainer postgreSQLContainer = PostgresTestContainer.getInstance();
}
PostgresTestContainer
public class PostgresTestContainer extends PostgreSQLContainer<PostgresTestContainer> {
public static final String IMAGE_VERSION = "postgres:13.5";
public static final String DATABASE_NAME = "test";
private static PostgresTestContainer container;
private PostgresTestContainer() {
super(IMAGE_VERSION);
}
public static PostgresTestContainer getInstance() {
if (container == null) {
container = new PostgresTestContainer().withDatabaseName(DATABASE_NAME);
}
return container;
}
#Override
public void start() {
super.start();
System.setProperty("DB_URL", container.getJdbcUrl());
System.setProperty("DB_USERNAME", container.getUsername());
System.setProperty("DB_PASSWORD", container.getPassword());
}
#Override
public void stop() {
}
}
In your test file extend the PostgresEnvironment
#ActiveProfiles("test")
#SpringBootTest(classes = MainSpringBootApp.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AssembleEventRepositoryIntegrationTest extends PostgresEnvrionement{
// autowire jpa
// write tests
}
in your application-test.yml file in the resources section under the test directory
spring:
datasource:
password: ${DB_USERNAME}
username: ${DB_PASSWORD}
driver-class-name: org.postgresql.Driver
url: ${DB_URL}
Also make sure that your main application.yml file that is being used once launching your application (not in for running your tests) matches the same syntax as your test profile.
I am using ReactiveMongo in the Spring Boot configured project.
service
#Document(collection = "tbl_created_service")
public class Services {
#Id
private ObjectId objectId;
private Long id;
// Getters and setters
}
ServiceReactiveRepository
public interface ServiceReactiveRepository extends ReactiveMongoRepository<Services, String>
{
}
ServiceService
public interface ServicesService {
Long addNewService(Services services);
}
ServiceImpl
#Service
public class ServicesServiceImpl implements ServicesService {
#Autowired
ServiceReactiveRepository serviceReactiveRepository;
#Override
public Long addNewService(Services servicesTypeDto) {
serviceReactiveRepository.save(servicesTypeDto);
return null;
}
application.yml
spring:
profiles:
active: dev
---
spring:
profiles: dev
data.mongodb:
host: 127.0.0.1
port: 27017
database: dev
---
MainApllication class
#SpringBootApplication
public class PartnersApplication {
public static void main(String[] args) {
SpringApplication.run(PartnersApplication.class, args);
}
}
When I am hitting the endpoint, I can see the data is binding and coming to ServicesServiceImpl.addNewService() but somehow it's not creating a new collection in DB (If not exists) i.e. tbl_created_service
However, If I add the collection manually and hit the service, Then it's creating a new document in "tbl_created_service".
Is the right way to do it? or I am missing something?
Any hint would be appreciable :)
just put this at your application.properties
spring.data.mongodb.uri=URIconnection
this will work when you will use an MongoDb atlas. Just create cluster and connect get the Java URI link then put it in your property. When you run your application it will auto generate the collections declared at your Entity class.
This is my application.properties file:
spring.data.mongodb.host: localhost
spring.data.mongodb.port: 27017
spring.datasource.url: jdbc:postgresql://localhost:5432/frontoffice
spring.datasource.username: frontoffice
spring.datasource.password: password
spring.datasource.driverClassName: org.postgresql.Driver
application.defaultLanguage: ca_ES
And this is my test:
#RunWith(SpringRunner.class)
#SpringBootTest()
public class GridFSTest {
#Autowired
private GridFsTemplate gridFsTemplate;
#Test
public void storeFile() throws IOException {
Path path = Paths.get(TestResources.CompatibleMatch.CSV_DOCUMENT.getPath());
InputStream is = Files.newInputStream(path);
this.gridFsTemplate.store(
is,
path.getFileName().toString(),
"text/plain",
null
);
}
#Test
public void getFile() {
Path path = Paths.get(
TestResources.CompatibleMatch.CSV_DOCUMENT.getPath()
);
GridFSFile gridFsFile = this.gridFsTemplate.findOne(
Query.query(
Criteria
.where("filename")
.is(path.getFileName().toString())
)
);
assertEquals(path.getFileName().toString(), gridFsFile.getFilename());
}
}
As you can see I'm only testing spring-data-mongodb related classes.
The point is that when I run this test, Spring is trying to load all resources, configuration which are not not related with my concrete test.
I'd like to load ONLY resources related with mongo, and remove the others.
Also, I'd like to have several *.properties file into my src/test/resources, one for each kind of context.
I know that exists ActiveProfiles anotation but, how could I annotate third-party library resources? spring-data-mongodb attemps to load its #Configuration classes, and I'm not able to change this behavior.
I hope I've explained so well.
How can I tell Spring to load only application-test.yml and not application.yml file ?
I have a config class:
#ActiveProfiles("test")
#SpringBootConfiguration
public class MongoTestConfig {
#Autowired
MongoOperations operations;
...
}
And a test class:
#RunWith(SpringRunner.class)
#DataMongoTest
#SpringBootTest(classes = MongoTestConfig.class)
public class TagDefinitionRepositoryTest {
...
#Test
....
}
I've tried to add :
#TestPropertySource(locations = {"classpath:application-test.yml"})
#ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
To my config class but it doesn't work: Spring still load application.yml
I don't think you can tell Spring Boot to ignore application.yml completely. What you can do though is to override all the non desired properties using test specific property files.
Based on the code snippet you posted, any property in application-test.yml will override the equivalent property in application.yml.
Spring Boot considers application-test.yml specific to the profile "test" (which has a higher priority over the default application.yml). No annotation #TestPropertySource is needed.
But if you want to choose another name for your properties file, then you can use #TestProertySource, since files indicated in #TestProperySource parameters have higher priority over the others.
You might want to have a look at Spring Boot external configuration rules for resolving properties
I've end up using #SpringBootTest instead of #DataMongoTest
#SpringBootConfiguration
#ComponentScan(basePackages = {"com.package.services"})
#EnableMongoRepositories(basePackages = {"com.package.repositories"})
public class MongoTestConfig {
private static final MongodStarter starter = MongodStarter.getDefaultInstance();
#Bean
public MongoClient mongoClient() throws IOException {
MongodExecutable _mongodExe;
MongodProcess _mongod;
_mongodExe = starter.prepare(new MongodConfigBuilder()
.version(Version.Main.V3_2)
.net(new Net("localhost", 12345, Network.localhostIsIPv6()))
.build());
_mongod = _mongodExe.start();
MongoClient _mongo = new MongoClient("localhost", 12345);
return _mongo;
}
#Bean
public MongoDbFactory mongoDbFactory() throws IOException{
return new SimpleMongoDbFactory(mongoClient() , "test");
}
#Bean
public MongoTemplate mongoTemplate() throws IOException {
return new MongoTemplate(mongoDbFactory());
}
And my test class is:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyRepositoryTest {
...
When testing the Hystrix fallback behavior of my Feign API, I get an error, when I expect it to succeed.
Feign interface:
This is the api to the external service.
#FeignClient(name = "book", fallback = BookAPI.BookAPIFallback.class)
public interface BookAPI {
#RequestMapping("/")
Map<String, String> getBook();
#Component
class BookAPIFallback implements BookAPI {
#Override
#RequestMapping("/")
public Map<String, String> getBook() {
Map<String, String> fallbackmap = new HashMap<>();
fallbackmap.put("book", "fallback book");
return fallbackmap;
}
}
}
Test class
This test exists just to verify fallback behavior:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = NONE)
public class BookServiceClientTest {
#MockBean
RestTemplate restTemplate;// <---- #LoadBalanced bean
#Autowired
private BookServiceClient bookServiceClient;
#Before
public void setup() {
when(restTemplate.getForObject(anyString(), any()))
.thenThrow(new RuntimeException("created a mock failure"));
}
#Test
public void fallbackTest() {
assertThat(bookServiceClient.getBook())
.isEqualTo(new BookAPI.BookAPIFallback().getBook().get("book")); // <--- I thought this should work
}
}
config files
application.yml
These files show configuration that might be relevant:
feign:
hystrix:
enabled: true
test/application.yml
eureka:
client:
enabled: false
The Question
Everything works fine when running the apps.
But when running this test, I get the below error.
Naturally, it's a test, so I'm trying to bypass the lookup anyway.
java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: book
at org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:71)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:97)
What am I missing?
Addendums
Application class
#SpringBootApplication
#EnableCircuitBreaker
#EnableDiscoveryClient
#EnableFeignClients
public class LibraryApplication {
public static void main(String[] args) {
SpringApplication.run(LibraryApplication.class, args);
}
}
LibraryController
#Controller
public class LibraryController {
private final BookServiceClient bookService;
public LibraryController(BookServiceClient bookServiceClient) {
this.bookService = bookServiceClient;
}
#GetMapping("/")
String getLibrary(Model model) {
model.addAttribute("msg", "Welcome to the Library");
model.addAttribute("book", bookService.getBook());
return "library";
}
}
There are no other classes.
so! I was able to recreate the issue, thanks for adding more code, had to play about with it a tad as I was unsure what the BookClientService looked like and it wouldn't make sense for it to implement the BookAPI as that would be an internal call e.g. in your application and not an external API call with Feign.
Anyway,
I pushed my version of what you provided here.
https://github.com/Flaw101/feign-testing
The issue was resolved when I renamed the second application.yml which lives in the src/test/resources folder to application-test.yml which will merge the properties.
The issue was caused by the fact the second property source, the testing one, overrides the initial application.yml and disables hystrix, because Hystrix is disabled there is no fallback to go to and it throws the root cause of what would cause the fallback, a lack of a server to call to for the Book API. Renaming it to application-test will always be loaded into spring test contexts. You could resolve it with the use of inlined properties or profiles.
I've added another test disabling feign /w hystrix within the test which re-creates the error you are recieving.