Spring Boot MongoRepository unit testing using pre-installed MongoDB - spring

I have a regular Spring Boot application (1.3.2) with MongoDB using MongoRepository.
I would like to write an integration test for one of my endpoints that gets the data from the MongoDB. As far as I see from the Spring Boot 1.3 Release Notes Spring has auto-configuration for Embedded MongoDB (de.flapdoodle.embed.mongo). However, I cannot figure out from Spring and flapdoodle documentation on how to write an integration test that would use already installed version of MongoDB on my filesystem.
So far my integration test looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(Application.class) // my application class
#WebAppConfiguration
public class IntegrationTest {
#Autowired
private MyRepository myRepository;
#Before
public void setup() {
myRepository.save(new MyEntity());
}
#Test
public void test() {
// here I will fire requests against the endpoint
}
}
I have added two dependencies with test scope: spring-boot-starter-test and de.flapdoodle.embed:de.flapdoodle.embed.mongo. So when I run the test, I can see that flapdoodle attempts to download version of MongoDB, but fails since I am behind the proxy. But I do not want to download any versions, I want it to use my locally installed MongoDB. Is it possible to do this?

If you want to use your locally installed MongoDB (not recommended, since then the tests depend on a particular DB that can get into a dirty state), then you shouldn't be pulling in the embedded MongoDB.
I believe this config will do what you're asking for, though (seems to work in my Spring Boot 1.3.5 test):
import java.net.UnknownHostException;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import com.mongodb.MongoClient;
#EnableAutoConfiguration(exclude = MongoAutoConfiguration.class)
#Configuration
public class TestConfig
{
#Primary
#Bean
MongoClient mongoClient()
{
try
{
return new MongoClient("localhost", 27017);
}
catch (UnknownHostException e)
{
throw new RuntimeException(e);
}
}
}
However, I suspect you would be better off properly configuring the proxy and using the embedded mongoDB in your tests. See this answer for hints on how to do that.

Related

Spring Boot Test with Cucumber and Mockito

I recently started to use Cucumber to implement a set of behavior driven tests for a Java project based on Spring Boot. I would like to call REST endpoints of this application using something like REST Assured or a custom REST client and for external systems and database I would like to setup some mock with Mockito, as I have already done with unit tests.
But I didn't find a complete working solution that I can apply to use Mockito beans in my Cucumber steps, for example to simulate a possible response from database queries.
I found a lot of posts of people over the years that had similar problems with different versions of Cucumber/Junit/Spring but I don't understand if it exists a right way to make these tools working together because I didn't a single complete example related to these tools together. Can anyone share experiences (versions/examples) in real world projects using Spring Boot Test, Cucumber and Mockito?
I finally found the solution. You can start the Cucumber test suite with a class like this
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;
#Suite
#IncludeEngines("cucumber")
#SelectClasspathResource("bdd")
#ConfigurationParameter(key = PLUGIN_PROPERTY_NAME,
value = "pretty")
#ConfigurationParameter(key = PLUGIN_PROPERTY_NAME,
value = "usage")
#ConfigurationParameter(key = PLUGIN_PROPERTY_NAME,
value = "html:target/cucumber-reports")
#ConfigurationParameter(key = GLUE_PROPERTY_NAME,
value = "myproject.cucumber.glue")
public class RunCucumberTest {
}
In the glue folder you can create a Spring Boot Test that will create the Spring Context and create the bridge between Spring Boot and Cucumber worlds thanks to CucumberContextConfiguration
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import io.cucumber.spring.CucumberContextConfiguration;
import myproject.repository.MyEntityRepository;
#CucumberContextConfiguration
#SpringBootTest
#ActiveProfiles("test")
public class SpringBootTestStarter {
#MockBean
private MyEntityRepository myEntityRepository;
}
Here you can use Mockito MockBean annotation and in the steps related to Cucumber scenarios you can now use this bean as a mock

Spring boot custom starter and Spring Data JPA - How to correctly provide repositories on my own custom autoconfigure/starter module

I am trying to write an autoconfigure / starter module for one of my project. This module handles the persistence through Spring Data JPA. It aims at providing several spring data repositories.
Right now, my autoconfiguration looks like this:
#Configuration(proxyBeanMethods = false)
#AutoConfigureAfter(JpaRepositoriesAutoConfiguration::class)
#EnableJpaRepositories(basePackageClasses = [ItemRepository::class])
#EntityScan(basePackageClasses = [ItemRepository::class])
class DomainPersistenceDataJpaAutoConfiguration() {
}
As stated in spring boot reference documentation, auto configuration should not enable component scanning, although #EnableJpaRepositories uses component scanning.
What would be a good alternative approach? Is there any example of existing spring boot start which provides repositories implementation I could consult?
What you're looking for is probably
AutoConfigurationPackages#register method, I think the common approach would be to implement ImportBeanDefinitionRegistrar and import this implementation in your autoconfiguration using #Import. A very elegant solution can be seen in Axon framework's RegisterDefaultEntities annotation and DefaultEntityRegistrar which it imports. This way your packages will be included in jpa and entity scans.
Edit: Adding actual example since as the review pointed out, the links might change over time.
In your case the ImportBeanDefinitionRegistrar could look something like this:
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class StarterEntityRegistrar implements ImportBeanDefinitionRegistrar {
#Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, ItemRepository.class.getPackageName());
}
}
and your autoconfiguration would be just:
#Configuration(proxyBeanMethods = false)
#AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
#Import(StarterEntityRegistrar.class)
class DomainPersistenceDataJpaAutoConfiguration() {
}

Unit testing Spring Boot application Service layer

I'm a bit confused about the test capabilities and utilities provided by Spring Boot.
I'm using spring-boot-starter-test in my project and I'd like to unit test my services without the database connection
At the moment I'm using #WebMvcTest for contoller test suites and #SpringBootTest for all the other test classes.
But I read somewhere that #SpringBootTest is meant to be used only in integration tests...
Reading documentation I didn't understood what's the suggested approach for services. Should I only test them in integration with repos?
UPDATE
That's an excerpt of a test class for one of my services:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest
internal class SignupServiceTest(
#Autowired val signupService: SignupService
) {
#MockkBean
lateinit var userRepository: UserRepository
#Test
fun `should return exception if username already used`() {
every { userRepository.findByUsername("registered-user") } returns fakeUser(username = "registered-user")
assertThatThrownBy {
signupService.createNewAccount(fakeSignupForm(username = "registered-user"))
}.isExactlyInstanceOf(UsernameNotAvailableException::class.java)
}
// ... other tests
}
Using #SpringBootTest for unit tests is a bit of a overkill. Because this would boot up the whole application context.
To test individual (service) classes I would go with #RunWith(MockitoJUnitRunner.class) and instead of #Autowired and #MockBean use #Mock and #InjectMocks(If you use constructor injection, you wouldn't have to use this. which would be the better option)
You could still use #Autowired with #ContextConfiguration and load specific classes(if there are not too many transitive dependencies)
If you do not want to use mocks, then you can use embedded databases and use #DataMongoTest or #DataJpaTest and use Springboot testing capabilities.
Keep it simple....
You should favor implementing unit tests to test your business logic (tests running without Spring, plain JUnit tests) over integration tests (tests starting a Spring container, #SpringBootTest) as they are more lightweight and give you feedback a lot faster.
Quote from Spring Boot doc
One of the major advantages of dependency injection is that it should make your code easier to unit test. You can instantiate objects by using the new operator without even involving Spring. You can also use mock objects instead of real dependencies.
Often, you need to move beyond unit testing and start integration testing (with a Spring ApplicationContext). It is useful to be able to perform integration testing without requiring deployment of your application or needing to connect to other infrastructure.
try to mock the calls to database using mockito, or use h2 database for the tests
I was previously using #SpringBootTest as well then realized it was also trying to connect to my database, which should not be needed since it's a simple service test and all dependencies are mocked. After a bit of research I found this solution works quite well. Also note now my spring properties were not being injected so I used #Spy to create a pojo property object with some test values.
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
/**
* This is the bean under test as it is not a mock
*/
#InjectMocks
private MyService myService;
#Mock
private OtherService otherService;
#Spy
private MyServiceProperties properties = mockProperties();
#BeforeEach
public void configureMocks() {
given(this.otherService.getData(any())).willReturn(mockDataResponse());
}
#Test
void testMyServiceReport_Success() {
myService.runReport();
//assert response as needed
verify(otherService, times(1)).getData(any());
}
Also here is some help with the imports which can sometimes be confusing:
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.BDDMockito.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.times;
import static org.mockito.BDDMockito.verify;

spring tool suite can not deploy example webservice on tomcat

I have some experience with EJB and JBoss and know the basics about Webservices, but I'm new to Spring.
So I tried to deploy the example Spring WS project gs-rest-service-complete without any changes. It is running on Spring Boot, but I cannot deploy and access it on an external Tomcat Server.
This is what I did: I installed Spring Tool Suite Version: 3.6.4.RELEASE and
Apache Tomcat 8.0.24 and defined Tomcat as a new Server in Spring Tool Suite.
It seems to work because I can deploy (and access) the example Spring MVC project and I can deploy another (not Spring Example) Webservice on Tomcat.
However I cannot deploy the gs-rest-service-complete project. I changed packaging in pom.xml to 'war', but it didn't help. Any hints what I could do?
Thanks, caduta
Finally I found the answer at the bottom of this site:
https://spring.io/blog/2014/03/07/deploying-spring-boot-applications
I had to do three steps to get it running:
change packaging to war in pom.xml.
comment out the declaration of the spring-boot-maven-plugin in pom.xml.
change the Application class to inherit from SpringBootServletInitializer and override method configure. This is necessary to register the application in tomcat.
Now the Application class looks like this:
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {
private static Class<Application> applicationClass = Application.class;
public static void main(String[] args) {
SpringApplication.run(applicationClass, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass);
}
}

How to configure JMX with Spring Boot

I have created a Spring Integration application with Spring Boot. I would like to know how to configure JMX with Spring Boot. I believe by default JMX is configured when using Spring Boot Actuator.
Do I need to configure anything else to be able to export MBeans for Spring Integration?
Most of the example I see have the following line in the applicationContext.xml
<context:mbean-export/>
<context:mbean-server/>
My Application.java class looks like this.
package com.jbhunt.app.consumerappointmentintegration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
#Configuration
#ComponentScan
#EnableAutoConfiguration
#ImportResource("classpath:META-INF/spring/integration/spring-integration-context.xml")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Adding this line to the configuration doesn't seem to export the Spring Integration mbeans
#EnableIntegrationMBeanExport(server = "mbeanServer", defaultDomain="my.company.domain")
I'm referencing this video https://www.youtube.com/watch?v=TetfR7ULnA8
As you understand the Spring Integration JMX is enabled by default, if you just have spring-integration-jmx in the classpath. And, of course, if spring.jmx.enabled = true (default).
You can't overrride that just declaring one more #EnableIntegrationMBeanExport, because it is based on #Import and you just can't override import classes because of (from ConfigurationClassParser):
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
If imported classes are already there, they aren't overridable.
You have several choices to achieve your requirements:
Disable default Spring Boot JMX - just addind to the application.properties spring.jmx.enabled = false and continue to use #EnableIntegrationMBeanExport
Configure IntegrationMBeanExporter #Bean manually.
Just configure your my.company.domain domain in the application.properties:
spring.jmx.default_domain = my.company.domain
It is quite late to add this; but in addition to the endpoints.jmx.domain I found it useful to change the spring.jmx.default-domain to someting unique per application
This is with multiple instances of Spring Boot 1.4.1 apps running in Tomcat 7

Resources