Strategy for unit testing a Spring Cloud Service - spring

Given the following Spring Cloud setup: A data-service with access to a database, an eureka-service to handle service registry and discovery and a third service business-service which will be one of various services which encapsulate business cases.
Unit testing the data-service is no problem, I just turn off eureka via
eureka.client.enabled=false
and use an in-memory database for my tests.
To access the data-service from business-service, I'm using an #FeignClient("data-service") annotated interface named DataClient which is #Autowired where needed. The service is discovered by Eureka, if both are running. This works fine for a production-like setup with all services running.
But now I want to unit test some features of my business-service. It wouldn't be a problem to start a test service with
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#IntegrationTest("server.port:0")
#SpringApplicationConfiguration(classes = Application.class)
like I'm doing in data-service. The problem is the Eureka-dependent discovery of my FeignClient... So my testclass crashes, because the autowiring my DataClient-instance doesn't work.
Am I abled to tell Spring to use a faked instance of DataClient just for my tests? Or is the only way to get my tests running an accessible, running instance of data-service and my Eureka server?

1, first create config bean, let the discovery client and feignclient only work when "eureka.enabled" is true
#Configuration
#EnableDiscoveryClient
#EnableFeignClients
#ConditionalOnProperty(name = "eureka.enabled")
public class EurekaConfig {
}
2, disable the eureka config for test profile, so in application-test.yml
eureka:
enabled: false
3, my project is build by maven, so i create a implement for my feign client interface, for example:
#Service
public class DataServiceImpl implements DataService {}
after this, when you run test in unit test with
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#IntegrationTest({"server.port=0", "management.port=0", "spring.profiles.active=test"})
public abstract class AbstractIntegrationTests {}
the fake service will inject in to spring context.
Or for normal unit test case, you can just need mockito mock the service class and use set method or construct method inject the mock object in your class

My first attempt crashed because of another bug... So it works fine with a #Configuration annotated class Conf which creates an fake implementation of DataClient like this:
#Bean
#Primary
public DataClient getDataClient() {
...
}
Added to my test via
#SpringApplicationConfiguration(classes = {Application.class, Conf.class})
the tested service instance uses the fake implementation correctly.

Adding on Yunlong's answer on annotating on a separate configuration class.
If the configuration class is placed under a different package than the root package, you will need to specify the "basePackages" for the #EnableFeignClients to scan for the annotated #FeignClient component.
com.feign.client.FeignClient.class
#FeignClient(value = "${xxxx}")
public interface FeignClient {
}
com.feign.config.EurekaConfig.class
#Configuration
#EnableFeignClients(basePackages = {"com.feign.client"})
#EnableEurekaClient
#ConditionalOnProperty(name = "eureka.enabled")
public class EurekaClientConfig {
}
Ps. I couldnt comment to the original reply so I created a new answer.

Related

how to correctly modularize app configuration, so that tests(IT,datajpa,...) does not pick up everything intended for production

In our app I found out, that my integration tests picks up more stuff than I'd like. I'd like to know, how correctly structured app configuration looks like, what do you use, so that I can #Import in tests only those configuration which are used in production, which are needed.
I believe relevant page in documentation is:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-user-configuration
... it's stressed there, that it's important to structure code in sensible way, however it's not shown that much, what that is/means. I know about profiles and can probably create profile which would be unmatched in tests and import manually, but that's probably not that sensible way they were talking about.
Consider this main entrypoint:
#SpringBootApplication
public class DemoApplication {
private final SomeService someService;
public DemoApplication(SomeService someService) {
this.someService = someService;
}
#EventListener(ApplicationReadyEvent.class)
public void started() {
System.out.println(someService.doIt());
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
interface of some service:
public interface SomeService {
public String doIt();
}
and configuration:
#Configuration
public class Config {
#Bean
public SomeService createSomeServiceBean() {
return new SomeService() {
#Override
public String doIt() {
return String.format("Hi! (At %s)", LocalDateTime.now());
}
};
}
}
When invoked, entrypoint annotated by #SpringBootApplication will do component scan, will discover configuration and it will work. Reading further in documentation we will find sentence: Test slices exclude #Configuration classes from scanning([if #ComponentScan does have default value of basePackages and basePackagesClasses]), however following test:
#SpringBootTest
class DemoApplicationTests {
#Autowired
private SomeService someService;
#Test
void contextLoads() {
System.out.println(someService.doIt());
}
}
just happily discovers SomeService bean defined. Or did that sentence meant just that tests annotated by for example #DataJpaTest won't register some configurations? Kinda unclear to me, but it does not seem possible, since how would #DataJpaTest would know, which configurations to ommit and which not.
Again, I know how to use profiles/excluding configurations. I'm asking about "sensible way of structuring app".
How to sensibly structure you app and how to configure it so that:
#SpringBootApplication annotated entrypoint will do component scan, find and use configurations, for production, but these configurations needs to be manually imported in tests?
some packages will be automatically scanned for configurations which will be used both in development and tests environments.
The Spring Boot Test support provides annotations that allow to only create a Spring Context with the relevant beans to testing a specific slice of your application.
There is no specific package structure or naming strategy required to make use of this feature.
Here are some of these:
#DataJpaTest: You get a Spring Context with relevant beans to test your JPA <-> Database interface: EntityManager, DataSource, all your interfaces extending JpaRepository
#WebMvcTest: You get a Spring Context with a mocked servlet environment for testing your web layer that includes the following beans for your: all your controller, controller advice, WebMvcConfigurer , Filter, etc. but not anything that is annotated with e.g. #Service or #Component
#SpringBootTest: This will give you a full Spring Context and tries to create all beans for you. You can exclude some autoconfiguration e.g. if you don't want autoconfiguration to kick in:
Example:
#SpringBootTest(webEnvironment = RANDOM_PORT)
#TestPropertySource(properties=
{"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration"})
There are way more test slice annotations, you can have a look at here
So these annotations are smart in a way that they know which beans they should include in the context and which to exclude.
A general approach to testing your application can be to use the first two test annotations stated above to verify web and data layer in isolation. Next use Mockito and plain JUnit 5 to unit test your service classes. And finally, write some integration test that creates the whole Spring Context with #SpringBootTest to test everything together.

Unable to find a SpringBootConfiguration in Spring Boot Test 1.4

I'm not able to run a simple test in spring boot 1.4. I followed the tutorial from the official site testing-the-spring-mvc-slice but I didn't get it to work.
every time i get the following error:
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
any ideas, hints?
Thanks in advance
Edit:
this is the controller
#Controller
public class UserManagementController {
#GetMapping(value = "/gs/users/getUsers")
public #ResponseBody String getAllUsers() {
return "test";
}
}
this is the test
#RunWith(SpringRunner.class)
#WebMvcTest(UserManagementController.class)
public class UserManagementControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void showUserView() throws Exception {
this.mvc.perform(get("/gs/users/getUsers"))
.andExpect(status().isOk())
.andDo(print());
}
}
From my point of view it's exactly the same like this post from the site.
the #WebMvcTest will do:
Auto-configure Spring MVC, Jackson, Gson, Message converters etc.
Load relevant components (#Controller, #RestController, #JsonComponent etc)
Configure MockMVC
now why i need to configure a "super" class
The search algorithm works up from the package that contains the test
until it finds a #SpringBootApplication or #SpringBootConfiguration
annotated class. As long as you’ve structure your code in a sensible
way your main configuration is usually found.
So you have annotated your test with #*Test. It run, checked for configuration in subclasses, haven't found any, thrown an exception.
You have to have a config in a package or subpackage of test class or directly pass config class to #ContextConfiguration or #SpringBootTest or have class annotated with #SpringBootApplication.
According to #SpringBootApplication. I have tested controller in way you have mentioned with #WebMvcTest: it works if application has class annotated as #SpringBootApplication and fails with exception you've mentioned if not. There is remark it the article you mentioned:
In this example, we’ve omitted classes which means that the test will
first attempt to load #Configuration from any inner-classes, and if
that fails, it will search for your primary #SpringBootApplication
class.
Github discussion about the same point.
Spring Boot Documentation

Spring Annotation #WebMvcTest does not work in an app that has Jpa repositories

I have a Spring App that uses JPA repositories (CrudRepository interfaces). When I try to test my controller using the new Spring test syntax #WebMvcTest(MyController.class), it fails coz it tries to instantiate one of my service class that uses JPA Repository, does anyone has any clues on how to fix that? The app works when I run it.
Here is the error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.myapp.service.UserServiceImpl required a bean of type 'com.myapp.repository.UserRepository' that could not be found.
Action:
Consider defining a bean of type 'com.myapp.repository.UserRepository' in your configuration.
According to the doc
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. #Controller, #ControllerAdvice, #JsonComponent Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not #Component, #Service or #Repository beans).
This annotion only apply on the Spring MVC components.
If you are looking to load your full application configuration and use MockMVC, you should consider #SpringBootTest combined with #AutoConfigureMockMvc rather than this annotation.
I was able to unit test a Rest Controller by implementing junit 5 and using #SpringJUnitConfig along with #WebMvcTest. I am using Spring Boot 2.4.5 and this is my example:
#SpringJUnitConfig
#WebMvcTest(controllers = OrderController.class)
class OrderControllerTest {
#Autowired
private MockMvc mockMvc;
// This is a Mock bean of a Spring Feign client that calls an external Rest Api
#MockBean
private LoginServiceClient loginServiceClient;
// This is a Mock for a class which has several Spring Jpa repositories classes as dependencies
#MockBean
private OrderService orderService;
#DisplayName("should create an order")
#Test
void createOrder() throws Exception {
OrderEntity createdOrder = new OrderEntity("123")
when(orderService.createOrder(any(Order.class))).thenReturn(createdOrder);
mockMvc.perform(post("/api/v1/orders").contentType(MediaType.APPLICATION_JSON).content("{orderId:123}"))
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))TODO: here it will go the correlationId
.andExpect(jsonPath("$.orderId").value("123"));
}
}
Please only use #SpringBootTest when you are implementing integration tests.
I faced this same problem. Using #SpringBootTest and #AutoConfigureMockMvc worked perfectly for me.

Spring tries to autowire Mockito mocks

I have a spring managed application in which I like for my service layer to be mocked. So I created a Spring Application Java Config and returned a mock of the actual service.
For e.g,
#Bean
#Profile("resource")
public MyService mockService() {
return mock(MyService.class)
}
And then the MyService goes as
class MyService {
#Autowired
private MyDao dao;
}
When Spring creates bean of name "mockService", it also tries to Autowire MyDao on the mock ? This in my opinion defeats the purpose of mocking. Is this the expected behaviour, whats the workaround ?
So bottomline, its best practice to code to interfaces rather than concrete classes particularly if you are wanting to write focused tests on specific layers.
Well, I suppose you're trying to execute an integration test, but using some mocks, doing something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {Application.class, YourMockConfigurationClass.class})
#WebIntegrationTest
public class MyIntegrationTest {
...
}
In this case, you can create a mock of your service, and tell spring that this service instance has priority:
#Bean
#Primary
public MyService mockService() {
return mock(MyService.class)
}
Doing this, whenever Spring has to inject an unqualified MyService instance, it always chooses your mock. But it doesn't prevent Spring to create the original service.
So inside Spring context there will be two instances of MyService, the mock instance and the original implementation. But using #Primary you're telling Spring that your mock has priority.
On the other hand, in order to prevent Spring to load any mock in your environments (dev, test, etc...), you should annotate your mock configuration class either with a testing #Profile or a custom annotation (like #MockBean) and then configure your component scan strategy to not load this class (#ComponentScan(excludeFilters=#Filter(MockBean.class)))

Spring-Boot module based integration testing

I have a multi-module Spring-Boot project.
I was wondering how I can set up integration testing just to test Spring Data JPA repositories? The following approach fails with this exception:
HV000183: Unable to load 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath.
Since this module does not depend on the web module, there is no web application that can be started.
#RunWith(SpringJUnit4ClassRunner.class)
#IntegrationTest
#SpringApplicationConfiguration(classes = TestConfiguration.class)
class CardInfoRepositoryIT {
#Autowired CardInfoRepository cardInfoRepository;
#Test
void testLoadData() {
assert cardInfoRepository.findAll().size() == 1
}
}
As Marten mentioned, #IntegrationTest should only be used when you need to test against the deployed Spring Boot application (e.g., deployed in an embedded Tomcat, Jetty, or Undertow container). So if your goal is to test your repository layer in isolation, you should not use #IntegrationTest.
On the other hand, if your tests require specific Spring Boot functionality (in contrast to standard Spring Framework functionality, semantics, and defaults), then you will in fact want to annotate your test class with #SpringApplicationConfiguration instead of #ContextConfiguration. The reason is that #SpringApplicationConfiguration preconfigures the SpringApplicationContextLoader which is specific to Spring Boot.
Furthermore, if you want your repository layer integration tests to run faster (i.e., without the full overhead of Spring Boot), you may choose to exclude configuration classes annotated with #EnableAutoConfiguration since that will auto-configure every candidate for auto-configuration found in the classpath. So, for example, if you just want to have Spring Boot auto-configure an embedded database and Spring Data JPA (with Hibernate as the JPA provider) along with entity scanning, you could compose your test configuration something like this:
#Configuration
#EnableJpaRepositories(basePackageClasses = UserRepository.class)
#EntityScan(basePackageClasses = User.class)
#Import({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
public class TestRepositoryConfig {}
And then use that configuration in your test class like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestRepositoryConfig.class)
#Transactional
public class UserRepositoryTests { /* ... */ }
Regards,
Sam
p.s. You might find my answer to the following, related question useful as well: Disable security for unit tests with spring boot
I resolved this by having the following test config class.
#Configuration
#EnableAutoConfiguration
#ComponentScan
#PropertySource("classpath:core.properties")
class TestConfiguration {
}
core.properties is also used by the main application and it contains datasource information. #IntegrationTest annotation can be removed on the test class.
I also added the following to the module as dependencies:
testRuntime 'javax.el:javax.el-api:2.2.4'
testRuntime 'org.glassfish.web:javax.el:2.2.4'

Resources