I plan to use Cassandra to save data reactively. To do that, I wrote the following interface:
#Repository
public interface ContributorStatRepository extends ReactiveCrudRepository<ContributorStat, Long> {
Flux<ContributorStat> findByAkonId(String akonId);
}
The exception above is thrown:
com.example.sample.controller.ContributorStatControllerTest > shouldReturnBadRequestWithBlankContributorStat FAILED
java.lang.IllegalStateException
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException
Do you know why the appropiate bean for ContributorStatRepository is not being created?
I am using Spring boot 2.0.0.M7 and these dependencies:
dependencies {
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('javax.xml.bind:jaxb-api:2.3.0')
compile('org.springframework.boot:spring-boot-starter-webflux')
compile('org.springframework.boot:spring-boot-starter-data-cassandra-reactive')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('io.projectreactor:reactor-test')
}
Updated:
Running test:
#Test
public void shouldReturnBadRequestWithBlankContributorStat() throws Exception {
requestPayload = mapper.writeValueAsString(new ContributorStatDTO());
this.mockMvc.perform(post(CONTRIBUTOR_STATS_ROUTE)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(requestPayload)).andDo(print())
.andExpect(status().isBadRequest());
}
It seems you're using an #WebMvcTest annotated class to test this case (I'm not sure though, that part is missing from your question).
To test Spring WebFlux applications, you should use #WebFluxTest (see reference documentation). Even if you do, the ContributorStatRepository bean won't be there since the web test slice will only consider the web parts of your application and you usually need to mock this one with #MockBean.
Related
I have a small standard Spring Boot microservice which is Spring Data REST for a single Mongo DB collection. The document contains the UUID field. The collection is a capped collection due to requirements.
Now I can build and run the app locally on my machine, no problem with that. However, we run everything in Docker on production. The container with the service is built without any issues, but once it starts, there is the following error:
{"timestamp":"2023-01-13T15:10:01.592Z","message":"Servlet.service()
for servlet [dispatcherServlet] in context with path [] threw exception
[Request processing failed; nested exception is
org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type [org.bson.types.Binary]
to type [java.util.UUID]] with root cause",
"component":"org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]",
"level":"ERROR","stack_trace":
"org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type [org.bson.types.Binary]
to type [java.util.UUID]
at org.springframework.core.convert.support.GenericConversionService
.handleConverterNotFound(GenericConversionService.java:322)
...
at org.springframework.data.mongodb.core.convert.MappingMongoConverter
.doConvert(MappingMongoConverter.java:1826)
...
at org.springframework.data.mongodb.repository.support.SimpleMongoRepository
.findAll(SimpleMongoRepository.java:444)
I have several questions actually:
Basically, how to handle it? Is it possible to handle using Spring configs?
Why SimpleMongoRepository.findAll() is called during the application start?
We have several applications with Mongo DB - they are good. What's wrong with Spring Data REST in this respect?
Why is the build OK, but when the container starts it fails?
Code samples are on Groovy just like the service (everything is public by default).
The repository is described as the following
#RepositoryRestResource( collectionResourceRel = 'events', path = 'events' )
interface EventRepository extends MongoRepository<EventMessage, String> { }
The collection configuration is the following
class CustomApplicationRunner implements ApplicationRunner {
#Autowired
MongoTemplate mongoTemplate
#Autowired
ApplicationProperties configuration
#Override
void run(ApplicationArguments args) throws Exception {
mongoTemplate.writeResultChecking = WriteResultChecking.EXCEPTION
if ( !mongoTemplate.collectionExists( EventMessage ) ) {
mongoTemplate.createCollection(EventMessage, new CollectionOptions(
configuration.mongoCollectionMaxSizeBytes,
configuration.mongoCollectionMaxDocuments,
configuration.mongoCappedCollection )
)
}
}
}
In application.yml the mongo configs are (on the development side)
mongodb:
uri: mongodb://mongodb:27017/development
auto-index-creation: true
uuid-representation: STANDARD
authentication-database: admin
The Spring Boot dependencies are
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-amqp'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-integration'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Versions: SpringBoot 2.7.7, Groovy 4.0.6
Now I did a little research.
Here is the JIRA issue for Mongo DB driver to support to/from UUID conversion for both Binary and BsonBinary. However, only BsonBinary is updated, apparently it is not used.
Here is Spring Data MongoDB issue for the same problem (or very close to mine). The author provided the solution, but I wonder if it is the only way. I'd like to reach the author for more details, but they have been inactive since then.
I can implement the solution from the above, but I'd like to figure out what's exactly is going on.
Thank you all in advance!
Working further on the problem I accidentally found a solution.
There was Spring Boot Starter Spring Integration dependency. However, nothing really from Spring Integration was used in the service.
There was also Jackson2JsonObjectMapper bean defined which wasn't used anywhere as well. Likely it was a leftover from the early stages of the service. Here I should note that the data class in question was annotated by #JsonDeserializer with the specific deserializer class which worked correct.
So removing of the bean and the Spring Integration dependency resulted in resolving the issue.
I have some integration tests, for which I am using Testcontainers. But I have suddenly realized that when my docker container with database for my application is down, all the other tests (excluding the integration tests using Testcontainers) are failing (even the contextLoads() test generated by Spring Boot initializr)
I get:
java.lang.IllegalStateException: Failed to load ApplicationContext at
org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'liquibase' defined in class path
resource
[org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration$LiquibaseConfiguration.class]:
Invocation of init method failed; nested exception is
liquibase.exception.DatabaseException:
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications
link failure
It is obvious that the application wants to connect to the database, and the database container is down.
I've been investigating, but I don't remember ever needing to start a container just for the test/build process of an application, so this problem is new for me. But if there is something done wrong, it could be here, in my AbstractDatabaseIT class:
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ContextConfiguration(initializers = AbstractDatabaseIT.DockerMySqlDataSourceInitializer.class)
#Testcontainers
public abstract class AbstractDatabaseIT {
private static final String MYSQL_IMAGE_NAME = "mysql:5.7.24";
public static final MySQLContainer<?> mySQLContainer = new MySQLContainer<>(MYSQL_IMAGE_NAME);
static {
mySQLContainer.start();
}
public static class DockerMySqlDataSourceInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(#NotNull ConfigurableApplicationContext applicationContext) {
Map<String, String> parameters = new HashMap<>();
parameters.put("command", "--character-set-server=utf8");
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
applicationContext,
"spring.datasource.url=" + mySQLContainer.getJdbcUrl(),
"spring.datasource.username=" + mySQLContainer.getUsername(),
"spring.datasource.password=" + mySQLContainer.getPassword()
);
mySQLContainer.setParameters(parameters);
}
}
}
The integration test extend this class:
public class ChallengeIT extends AbstractDatabaseIT {
#Autowired
private ChallengeRepository repository;
// tests here
All the other, non-integration classes have #SpringBootTest annotation, and the dependencies injected using #Autowired (maybe this is a problem here?)
#SpringBootTest
class EthMessageVerifierTest {
#Autowired
private EthMessageVerifier ethMessageVerifier;
// tests here
What am I missing here? I remember seeing the H2 database dependency all around many projects. Should I drop the testcontainers in favour of H2? Or can I somehow create a single testcontainer instance for all the other tests?
Tests that you annotate with #SpringBootTest try to populate the entire Spring context. This includes all your beans: your web layer, your business logic, your database setup, etc.
Hence all the infrastructure (e.g. messaging queues, remote systems, databases) that you need otherwise to run your entire application also needs to be present for such tests.
So #SpringBootTest also indicates an integration test and you need to provide your database setup as on application start, Spring Boot's auto-configuration tries to configure your DataSource.
For more information, consider this article on #SpringBootTest and this general overview about unit & integration testing with Spring Boot. You don't always have to use #SpringBootTest and can also use one of Spring Boots many test slice annotations to test something in isolation.
I have a Spring Boot REST service and some unit tests written for the data layer. I use the embedded MongoDB dependency to perform the basic CRUD tests for my Repository class:
public interface UserRepository extends MongoRepository<UserEntity, String> {
Optional<UserEntity> findByUsername(String username);
}
I load the data from a JSON file (located under test/java/resources/data) and with the help of an ObjectMapper instance, I load the data into the embedded DB before each test and drop the collection after it's completed:
#DataMongoTest
class UserRepositoryTest {
// the path to the JSON file
private final File USER_DATA_JSON = Paths.get("src", "test", "resources", "data", "UserData.json").toFile();
// used to load a JSON file into a list of Users
private final ObjectMapper objectMapper = new ObjectMapper();
#Autowired
private MongoTemplate mongoTemplate; // makes the interaction with the embedded MongoDB much easier
#Autowired
private UserRepository userRepository;
#BeforeEach
void setUp() throws IOException {
// deserialize the JSON file to an array of users
UserEntity[] users = objectMapper.readValue(USER_DATA_JSON, UserEntity[].class);
// load each user into embedded MongoDB
Arrays.stream(users).forEach(mongoTemplate::save);
}
#AfterEach
void tearDown() {
// drop the users collection
mongoTemplate.dropCollection("users");
}
#Test
void testFindAllSuccess() {
// WHEN
List<UserEntity> users = userRepository.findAll();
// THEN
assertEquals(2, users.size(), "findAll() should return 2 users!");
}
// other test methods
}
In my local environment, everything works just fine, no configuration is needed inside the application.properties file. But when I execute a build on Jenkins, the following error appears for all the Repository tests:
UserRepositoryTest > testFindAllSuccess() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
Caused by: org.springframework.beans.factory.BeanCreationException at ConstructorResolver.java:658
Caused by: org.springframework.beans.BeanInstantiationException at SimpleInstantiationStrategy.java:185
Caused by: java.net.UnknownHostException at InetAddress.java:1642
Caused by: java.net.UnknownHostException at Inet6AddressImpl.java:-2
The build.gradle file dependencies are declared as follows:
implementation 'org.openapitools:openapi-generator-gradle-plugin:5.0.0'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation "io.springfox:springfox-boot-starter:3.0.0"
implementation('org.modelmapper:modelmapper:2.3.0')
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo'
testImplementation 'io.projectreactor:reactor-test'
compile 'io.springfox:springfox-swagger-ui:3.0.0'
annotationProcessor group: 'org.springframework.boot', name: 'spring-boot-configuration-processor'
compile 'org.mongodb:mongodb-driver-sync'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'
// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
I assume Spring Boot is not identifying the embedded MongoDB anymore. How can I configure it as simple as possible?
I saw there is a possibility of using containerized MongoDB and test it using #TestContainers, but for now I need to fix this issue since every build fails due to the tests.
LATER EDIT: By activating the --debug option in Jenkins when running the build, I discovered the following cause:
java.net.UnknownHostException: dev096.dev.cloud.******.eu: dev096.dev.cloud.******.eu: Name or service not known
Do you know if I have to add that address to the known hosts to my local machine (etc/hosts) or it should be configured locally with profiles (localhost for development and dev096.dev.cloud.******.eu for production)?
I managed to reproduce the same error locally and, if you haven't solved it yet, this is my take.
Remove all MongoDB custom configurations/properties, then add the following class to your project:
#Configuration
public class MongoTestConfig {
#Bean
MongoProperties properties() {
MongoProperties mongoProperties = new MongoProperties();
mongoProperties.setPort(33333);
mongoProperties.setHost("localhost");
return mongoProperties;
}
}
Make sure you tag your test class (UserRepositoryTest) with these three annotations:
#RunWith(SpringRunner.class)
#DataMongoTest
#Import(MongoTestConfig.class)
#DataMongoTest ignores all other bean definitions so we force the test to include the configuration that we just created using the explicit #Import.
Now the test will be forced to run on localhost. Hope it's going to work for you as it did for me! 😁
If dev096.dev.cloud.******.eu is a host used only in production, then your production server needs to know that host; not your local PC, nor Jenkins.
Ideally, you'd run your Jenkins tests using a 'jenkins' Spring profile, and then define the localhost in application-jenkins.properties.
Unfortunately, none of your solutions worked for me, but I found out later that dev096.dev.cloud.******.eu is the Jenkins instance itself.
The fix was to add an entry in /etc/hosts/ such that the server's IP to be also recognized as dev096.dev.cloud.******.eu
I have an application using SpringBoot2 with mongodb and I am trying to test json serialization of some DTOS by making tests like:
#JsonTest
#RunWith(SpringRunner.class)
public class SomeDTOTest {
#Autowired
JacksonTester < SomeDTO > json;
#Test
public void someTest() {}
}
However underneath spring is trying to create repository bean and giving me informations:
***************************
APPLICATION FAILED TO START
***************************
Description:
A component required a bean named 'mongoTemplate' that could not be found.
Action:
Consider defining a bean named 'mongoTemplate' in your configuration.
I have more integration test that is using the repository and are annotated with #SpringBootTests and they are working fine...
Is there a way of restricting spring to only creating JacksonTester bean?
You could just create a test without spring runner.
This is an example example test
When loading the spring context if there is an autowired annotation of a mongotemplate somewhere spring will try to provide it. You might consider:
Provided mongo template in tests
Try using #DataMongoTest which will provide an embedded database.
Set an Autowired not required
Use #Autowired(required= false)
Mock mongotemplate
Use #MockBean annotation in order to mock mongoTemplate
I found it quite challenging to have both Integration tests as well as Unit tests in a Spring Boot application.
I checked Spring website and tried many solutions. The one that worked for me was to exclude the AutoConfiguration classes:
#RunWith(SpringRunner.class)
#JsonTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)
public class JsonTests {
#Autowired
private JacksonTester json;
#MockBean
private MyRepository repository;
#MockBean
private MongoTemplate mongoTemplate;
#Test
public void someTest() {}
}
You can find a complete Spring Boot application that include Integration and Unit tests here.
My code has many integration tests classes - ServiceTest1, ServiceTest2 ... , ServiceTestN.
All tests are executed with spring using the same test context (MyAppContext.xml).
As shown in the snippet below, ServiceTest1 test context requires one of its beans to be overridden, and that change is relevant ONLY for ServiceTest1.
When executed, ServiceTest1 works as expected. However, when executing the other tests (e.g. ServiceTest2 in the snippet) it fails with spring initialization errors, since spring is caching MyAppContext.xml test context, and ServiceTest1 test context manipulation is conflicting with the other tests.
I'm looking for a way to avoid caching of ServiceTest1 test context (such as making its caching key unique, based on #ContextConfiguration).
Note that fully disabling caching is not a wanted solution.
Any ideas?
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class ServiceTest1 {
// some tests ...
#Configuration
#ImportResource({"classpath*:MyAppContext.xml"})
static class Context {
#Bean
#Primary
ServiceOne serviceOne() {
// instantiate something ...
}
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath*:MyAppContext.xml"})
public class ServiceTest2 {
// some tests ...
}
Thanks!
EDIT:
Spring initialization errors are:
java.lang.IllegalStateException: Failed to load ApplicationContext
...
Caused by: org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Failed to import bean definitions from URL location [classpath:/WEB-INF/applicationContext-security]
Offending resource: URL [file:/path/to/MyAppContext.xml]; nested exception is org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Duplicate detected.
Offending resource: class path resource [WEB-INF/applicationContext-security.xml]
Use the #DirtiesContext annotation, add this at the class level.
It will then refresh the spring application context after the test class has executed.
Documentation: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/annotation/DirtiesContext.html