Springboot Webflux endpoint not found by test - what is causing it? - spring-boot

I have the following code, and am consistently receiving 404 not found errors? Any advice would be much appreciated!
I've researched conflicting dependencies which does not seem to be the problem. I've also ensured that I am returning the correct content type.
One thing that I am not sure I'm doing correctly is annotating with the Bean and Autowired annotations. I have little understanding of what those do at the moment.
Router class below
#Configuration
#AllArgsConstructor
public class AgencyRouter {
#Bean
public RouterFunction<ServerResponse> agencyRoutes(AgencyController agencyController) {
return RouterFunctions
.route(RequestPredicates.POST("/agency").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), agencyController::createAgency);
}
}
Controller/Handler class below
#Component
public class AgencyController {
public Mono<ServerResponse> createAgency(ServerRequest request){
return ServerResponse.ok()
// .contentType(MediaType.APPLICATION_JSON)
.body(
Flux.just("Test", "message")
.delayElements(Duration.ofSeconds(1)).log(), String.class
);
}
}
Test class
#AutoConfigureWebTestClient
public class AgencyControllerTest {
#Autowired
private WebTestClient webClient;
#Test
void testCreateAgency() {
AgencyRequest request = new AgencyRequest(new AgencyRequestFields("TestName", true));
webClient.post()
.uri("/agency")
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(request))
.exchange()
.expectStatus().is2xxSuccessful();
}
}
build.gradle dependencies
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
//implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.flywaydb:flyway-core'
implementation 'org.springdoc:springdoc-openapi-webflux-ui:1.5.2'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:postgresql'
}
Thanks in advance!!!

The easiest way is just to add #SpringBootTest on your AgencyControllerTest class (along with #AutoConfigureWebTestClient which you already have):
#SpringBootTest
#AutoConfigureWebTestClient
public class AgencyControllerTest {
...which sets up full auto-configuration along with your web test client. Then you don't need to do anything else - all your controllers, routes etc. will be available. This is probably easiest to remember if you're just getting started.
Alternatively, you can use #WebFluxTest and #ContextConfiguration to just instantiate the context you need for this particular test:
#WebFluxTest
#AutoConfigureWebTestClient
#ContextConfiguration(classes = {AgencyController.class, AgencyRouter.class})
public class AgencyControllerTest {
If you have other routes, controllers, beans, etc. set up and only need a small subset then this approach is more efficient, as you're not setting up and tearing down the entire context for each test (only what you need.)
One thing that I am not sure I'm doing correctly is annotating with the Bean and Autowired annotations. I have little understanding of what those do at the moment.
I'd recommend taking a good look at dependency injection & inversion of control (both theoretically and in a Spring context) - this is pretty much the foundation of Spring, and you'll almost certainly come unstuck at some point unless you have (at least) a base level understanding of this.

Related

Using lombok dependency in my spring-boot project but the getter method yields error at runtime (built successfully though)

I am developing a spring-boot project using Gradle as the build tool on ItelliJ IDE.
I have the dependency of lombok declared in gradle.build:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:2.5.3'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
compileOnly 'org.projectlombok:lombok:1.18.20'
}
I have a model class:
import lombok.Data;
import java.math.BigDecimal;
#Data
public class ProductModel {
private String name;
private BigDecimal price;
private Integer quantity;
}
As you can see I have annotated with #Data.
My controller has the method to handle a POST request, its payload is mapped to the ProductModel:
#PostMapping
public String createProduct(#RequestBody ProductModel productPayload) {
// Runtime error: error: cannot find symbol, 'getName' in 'ProductModel'
productPayload.getName();
}
I know I need to install the lombok plugin on my IntelliJ IDE in order to avoid compiler error on the getter method. So I did that. But when I run my application I get error:
error: cannot find symbol
symbol: method getName()
location: variable productPayload of type CreateProductRestModel
I also tried change the dependency from compileOnly to implementation:
implementation 'org.projectlombok:lombok:1.18.20'
It doesn't help. Why is that? What am I missing?
(I have enabled annotationProcessor on my IntelliJ too)
In order for Gradle to pick up on annotation processors, they have introduced a separate configuration that will generate all the new code ahead of the "normal" compilation.
For Lombok, it would look something like this:
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.20'
annotationProcessor 'org.projectlombok:lombok:1.18.20'
testCompileOnly 'org.projectlombok:lombok:1.18.20'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
}

SpringBoot - Can't resolve #RunWith - cannot find symbol

SpringBoot project.
In build.gradle:
dependencies {
implementation 'com.google.code.gson:gson:2.7'
implementation 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml'
implementation 'com.squareup.okhttp3:logging-interceptor:3.8.0'
implementation('com.squareup.retrofit2:retrofit:2.4.0')
implementation('com.squareup.retrofit2:converter-gson:2.4.0')
implementation group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
Here my test class:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
#RunWith(SpringRunner.class)
#DataJpaTest
public class CategoryRepositoryIntegrationTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private CategoryRepository productRepository;
But I get error:
error: cannot find symbol
#RunWith(SpringRunner.class)
^
symbol: class RunWith
1 error
FAILURE: Build failed with an exception
You mixed JUnit 4 and 5. You use the Test annotation from JUnit 5 and the RunWith annotation is from JUnit 4. I would recommend using JUnit 5. For this you only need to replace RunWith with the following line:
#ExtendWith(SpringExtension.class)
Or if you use SpringBoot 2.1 or older, you can remove the RunWith annotation and it should also work.
I found this from Spring boot doc, not sure could help with your question.
If you are using JUnit 4, don’t forget to also add #RunWith(SpringRunner.class) to your test, otherwise the annotations will be ignored. If you are using JUnit 5, there’s no need to add the equivalent #ExtendWith(SpringExtension.class) as #SpringBootTest and the other #…Test annotations are already annotated with it.
here is the link https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/html/boot-features-testing.html
and check section 46.3

Spring #DataJpaTest with JUnit 5

There doesn't seem to be a specific standard way I can find online that makes #DataJpaTest to run correctly.
Is it true that #DataJpaTest is not being used nowadays and all tests are run at the service or controller level using #SpringBootTest?
#Repository
public interface MyBeanRepository extends JpaRepository<MyBean, Long> {
}
#Configuration
#EnableJpaRepositories("com.app.repository.*")
#ComponentScan(basePackages = { "com.app.repository.*" })
public class ConfigurationRepository {
}
#Entity
public class MyBean {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version;
#NotNull
#Size(min = 2)
private String name;
}
#DataJpaTest
public class MyBeanIntegrationTest {
#Autowired
MyBeanRepository myBeanRepository;
#Test
public void testMarkerMethod() {
}
#Test
public void testCount() {
Assertions.assertNotNull(myBeanRepository , "Data on demand for 'MyBean' failed to initialize correctly");
}
}
Running using the Eclipse->Run Junit shows these logs.
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
at org.springframework.util.Assert.state(Assert.java:73)
Running using gradle test shows the error that init failed.
FAILURE: Build failed with an exception.
* What went wrong:
Test failed.
Failed tests:
Test com.app.repository.MyBeanIntegrationTest#initializationError (Task: :test)
Here is the gradle script.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin")
}
}
plugins {
id 'org.springframework.boot' version '2.1.5.RELEASE'
id 'java'
id 'eclipse'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.app'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenLocal()
jcenter()
mavenCentral()
}
test {
useJUnitPlatform()
}
dependencies {
// This dependency is exported to consumers, that is to say found on their compile classpath
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
runtimeOnly 'org.hsqldb:hsqldb'
testImplementation ('org.springframework.boot:spring-boot-starter-test') {
// exlcuding junit 4
exclude group: 'junit', module: 'junit'
}
/**
Test Dependencies Follows
**/
// junit 5 api
testCompile "org.junit.jupiter:junit-jupiter-api:5.2.0"
// For junit5 parameterised test support
testCompile "org.junit.jupiter:junit-jupiter-params:5.2.0"
// junit 5 implementation
testRuntime "org.junit.jupiter:junit-jupiter-engine:5.2.0"
// Only required to run junit5 test from IDE
testRuntime "org.junit.platform:junit-platform-launcher"
}
EDIT:
This has been solved and committed at the same repository.
https://github.com/john77eipe/spring-demo-1-test
It was a good idea to add a Github link. I can see following issues there:
1) If you can't have a main class annotated with #SpringBootApplication, you can use:
#SpringBootConfiguration
#EnableAutoConfiguration
#ComponentScan(basePackages = "com.app.repository")
public class MySampleApplication {
}
2) Change annotations over your ConfigurationRepository class to:
#EnableJpaRepositories("com.app.repository")
#ComponentScan(basePackages = { "com.app.repository" })
public class ConfigurationRepository {
That should let us proceed to the next point:
3) Your MyBeanIntegrationTest should be annotated as:
#SpringBootTest(classes = MyAppApplication.class)
public class MyBeanIntegrationTest {
4) In application.yml you have a small issue with indentation in the last line. Convert tab so spaces and it should be fine.
5) Next thing is MyBeanRepository interface. You can't use a method named findOne there. Thing is, that in interfaces marked as JpaRepository or CrudRepository and so on, methods names need to follow certain rules. If you mark that it will be a repository containing type MyBean your method name should be changed to findById, because Spring will look for a property named id in your bean. Naming it by findOne will cause test to fail with:
No property findOne found for type MyBean!
After fixing these things, your tests pass on my env.
I hope this helps!

The dreadful "Unable to start EmbeddedWebApplicationContext" error

I have the following exception when trying to run integration test:
org.springframework.context.ApplicationContextException: Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
I read many forum entries but not found any solution. My files are as follows:
Integration test
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = WebInitializer.class)
#DataJpaTest
#Sql("/db/data.sql")
public class ReportEventIntTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void reportEvent() {
Map<String, String> eventMap = new HashMap<>();
this.restTemplate.postForEntity("/worker/event", eventMap, String.class);
}
}
Spring Boot config
#Configuration
#ComponentScan(basePackages = {"org.reaction.engine.collector.controller",
"org.reaction.engine.persistence.service",
"org.reaction.engine.persistence.converter",
"org.reaction.engine.service"})
#EnableAutoConfiguration
#ImportResource("classpath:applicationContext.xml")
#Profile("threadPool") // define the default profile: it can be overridden by -Dspring.profiles.active=...
public class WebInitializer extends SpringBootServletInitializer implements WebApplicationInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(WebInitializer.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(WebInitializer.class, args);
}
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
return factory;
}
}
Gradle file
apply plugin: 'org.springframework.boot'
apply plugin: 'war'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.4.3.RELEASE'
}
}
dependencies {
compile project(':common')
compile 'org.springframework:spring-context-support'
compile 'org.springframework.boot:spring-boot-starter'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
compile 'org.springframework.boot:spring-boot-starter-integration'
compile 'org.springframework.boot:spring-boot-starter-actuator'
compile 'org.apache.httpcomponents:httpclient:4.5.2'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
providedCompile 'javax.enterprise.concurrent:javax.enterprise.concurrent-api:1.0'
runtime 'mysql:mysql-connector-java'
// ---------------------- TESTING ----------------------
testCompile 'com.jayway.restassured:rest-assured:2.9.0'
testCompile 'org.springframework.boot:spring-boot-starter-test'
//testRuntime 'org.hsqldb:hsqldb'
testRuntime 'com.h2database:h2'
testRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}
Any idea?
I would greatly appreciate any thought!
Regards,
V.
I read many stackoverflow entries but I missed the following one:
#Profile cause Unable to start EmbeddedWebApplicationContext
The problem is that I defined the profile in my spring boot config class but I have to do that in the test class.
#ActiveProfiles("threadPool")

[JUnit Unit test cases]: java.lang.NoClassDefFoundError: org/apache/commons/logging/Log

Getting java.lang.NoClassDefFoundError: org/apache/commons/logging/Log as I mock. Refer code
#Mock
private RestTemplate restTemplate;
Dependencies we included
testCompile "org.apache.logging.log4j:log4j-slf4j-impl:2.5"
testCompile "org.apache.logging.log4j:log4j-core:2.5"
testCompile "org.slf4j:jcl-over-slf4j:1.7.21"
testCompile "commons-logging:commons-logging:1.1.1"
Note: We are using slf4j logging.
For actual application these dependencies are resolved by tomcat server.
First thing you need to keep in mind is -- "you are using a mock object" and mock is just a place holder, it is not real object. So, you have to define its behavior and dependencies. the template is defined as mock, so you have to inject any dependent objects like logger. you can achieve that by using something like this
#RunWith(MockitoJUnitRunner.class)
public class MyTest {
#Mock
Logger logger;
#InjectMocks
private RestTemplate restTemplate;
#Test
public void isLoggerGettingCalled() throws Exception {
// Your test logic
}
}
I think this is one of those rare cases where you do not actually miss the specific class, but have one to many of them. If you check these two jars that you imported:
testCompile "org.slf4j:jcl-over-slf4j:1.7.21"
testCompile "commons-logging:commons-logging:1.1.1"
You will see, that both of them have the following class in them:
org/apache/commons/logging/Log
The classloader encountered this duplication and could not load the class definition. If you are looking for the right combination of slf4j jars, I would go with either of these two options:
slf4j-api-[latest-version].jar
slf4j-simple-[latest-version].jar
OR
slf4j-api-[latest-version].jar
slf4j-log4j12-[latest-version].jar
log4j-[latest-version].jar
But in the end there are many combinations to choose from depending on your preference.

Resources