How can I use #SpringBootTest(webEnvironment) with #DataJpaTest? - java

I am trying to test a JAX-RS application but I'd rather not mock the data especially since there's a buildData method for an existing #DataJpaTest
Here's what I am trying so far:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = MyApp.class
)
#DirtiesContext
#DataJpaTest
public class MyResourceTest {
I get the following error
java.lang.IllegalStateException: Configuration error: found multiple declarations of #BootstrapWith for test class [app.MyResourceTest]: [#org.springframework.test.context.BootstrapWith(value=class org.springframework.boot.test.context.SpringBootTestContextBootstrapper), #org.springframework.test.context.BootstrapWith(value=class org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper)]
The other ones I saw that are similar do not talk about the webEnvironment setting:
How do I import configuration classes in a #DataJpaTest in a SpringBootTest?
Recommended way to remove #DataJpaTest when used along with #SpringBootTest
There is somewhat of a solution using #AutoConfigureTestDatabase but when I did that only the first one works because buildData is annotated with #Before (same as in #DataJpaTest) as I want the data to be pristine before each test so I can do failure scenarios.
Switching to #BeforeClass also won't work because I won't be able to use the #Autowire Repository objects.

The #DataJpaTest documentation states the following:
If you are looking to load your full application configuration, but use an embedded database, you should consider #SpringBootTest combined with #AutoConfigureTestDatabase rather than this annotation.
Keep in mind that #DataJpaTest is annotated with #Transactional and #DirtiesContext. So you may need those annotations along with #AutoConfigureTestDatabase.

Actually when doing the answer in https://stackoverflow.com/a/57609911/242042, it solves the immediate problem, but you won't be able to do any tests that involve the database using a REST client as #Transactional will prevent the data from being saved for the client to get.
To get this to work, #Transactional should not be used. Instead DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD should be used instead. This dramatically slows down each test (as in 1 second to 10 seconds per test) but at least it works.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = MyApp.class
)
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
#AutoConfigureTestDatabase
#AutoConfigureWebTestClient
public class MyResourceTest {
#Autowired
private TestRestTemplate restTemplate;
...
}

Related

Recommended way to remove #DataJpaTest when used along with #SpringBootTest

We have an application that relies on Spring Boot 2.0. We are in the process of migrating it to JDK11 from JDK8. This also enabled us to update Spring Boot from 2.0 to 2.1. After reading through the changelog, it appeared there was any major change that needed for us.
Now the problem lies in where some test classes are annotated with both #SpringBootTest and #DataJpaTest. As per this and as well as the documentation, we are not supposed to use both together and instead we changed #DataJpaTest to #AutoConfigureTestDatabase. Here is how the code is:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {A.class, B.class}, properties = {
"x=xxx",
"y=yyy"
})
#AutoConfigureTestDatabase // Used to be #DataJpaTest
#EnableJpaRepositories("com.test")
#EntityScan("com.test")
public class Test {
#TestConfiguration
public static class TestConfig {
// Some beans returning
}
// Tests
}
Now, we end up with the following error:
NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available
So as per this answer, we did something like this:
#EnableJpaRepositories(basePackages="com.test", entityManagerFactoryRef="entityManagerFactory")
Even after this we still end up with the same error. Is this the right way to remove #DataJpaTest? Or do we need to remove #SpringBootTest and do something else? Any sort of guidance is much appreciated.
The testclass is annotated with #DataJpaTest and #ContextConfiguration
#RunWith(SpringRunner.class)
#DataJpaTest
#ContextConfiguration(locations = { "classpath:test-context.xml" })
public abstract class AbstractTestCase {
protected static final Logger LOG = LoggerFactory.getLogger(AbstractTestCase.class);
}
We defined a test-context.xml. This is because the testmodule is isolated from all other modules (multi maven module project). In the test-context.xml we defined the component-scan for the base-package.
<context:component-scan base-package="de.example.base.package" />

Minimizing classes to load for JUnit testing

Let's say I'm testing a repository :
#RunWith(SpringRunner.class)
#SpringBootTest
public class UserRepositoryTest {
#Test
(...)
#Test
(...)
}
I'm ok that spring loads other repositories, but I'm not ok it loads the embedded Tomcat, the services, the controllers, ... every time I launch one of these JUnit.
What is the simplest way to achieve this?
I've tried to put some inner #Configuration class with a #ComponentScan limited to my repository package but it didn't work (it was just ignored).
Use the annotation #DataJpaTest instead of #SpringBootTest. It only loads the persistence related part of Spring.
#RunWith(SpringRunner.class)
#DataJpaTest
public class UserRepositoryTest {
#Test
(...)
#Test
(...)
}
You will find a detailed solution here
If you have some usage of JdbcTemplate then take a look to this answer
It looks like there is not one single answer to this question.
Of course, for JPA repositories, Lore answer is the best : use #DataJpaTest (or #JdbcTest for my use case). But be also sure to use "#AutoConfigureTestDatabase(replace = Replace.NONE)" if your test data is in your database and not in some in-memory one.
Also there is a special chapter talking about this in Spring doc :
Spring Boot’s auto-configuration system works well for applications
but can sometimes be a little too much for tests. It often helps to
load only the parts of the configuration that are required to test a
“slice” of your application.
source : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-tests
But it doesn't show all you can/need to do.
For example, I had a smtpClientService to test.
To test this service, alone in its own layer, I had to do these specific adaptations (if I omit "#AutoConfigureWebClient", I won't get RestTemplateBuilder injected) :
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureWebClient
public class smtpClientServiceTest {
#Autowired
SmtpClientService service;
#Configuration
#Import(SmtpClientConfig.class)
#ComponentScan(basePackageClasses = SmtpClientService.class)
static class TestConfiguration {
}
#Test
public void testSendMessage() {
(...)
}
}

Spring Boot with spring-boot-starter-data-jpa in unit test needs mandatory #DataJpaTest

I'm testing Spring Boot capabilities as a newbie in this area. I have a simple app with basic dependencies.
sping-boot-starter-parent 1.5.7
sping-boot-starter
sping-boot-starter-data-jpa
sping-boot-starter-test
Then there is simple Application class with #SpringBootApplication annotation.
Then I have a simple DummyService with #Service annotation.
Then I have created a simple test DummyServiceTest with one #Test method and #RunWith(SpringRunner.class) and #SpringBootTest annotations.
#SpringBootTest is the key problem. With spring-boot-starter-data-jpa dependency and this annotation test requires even #DataJpaTest annotation. Without it the framework doesn't solve HibernateJpaAutoConfiguration or DataSource or other injection dependencies even if the test doesn't require using data.
Can I suppress it somehow? I'm not any kind of Spring guru so my guess is that there is some simple configuration to handle this problem.
P.S. Ok, back on trees. Even with #DataJpaTest that test doesn't solve data dependencies. I tried add #AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) and it doesn't work either. I tried add #Transactional with the same result. It's a little bit beyond ridiculous.
If you aren't using JPA yet, then comment out / remove the dependency from build.gradle until you are using JPA. You need to define a datasource and other configuration details before the Hibernate and/or JPA configuration will complete successfully. Every application dependency gets resolved while #SpringApplicationConfiguration code is running, even if your current "hello world" test doesn't require JPA data.
My current unit tests actually have #SpringBootTest commented out. Here's a simplified view of how things are set up and working in my app's JPA related tests:
#RunWith(SpringJUnit4ClassRunner)
#SpringApplicationConfiguration(classes = DaemonApplication)
#ActiveProfiles('local')
//#SpringBootTest
#Transactional
public abstract class AbstractJpaTest extends AbstractTransactionalJUnit4SpringContextTests {
#BeforeTransaction
public void setupData() throws Exception {
deleteFromTables('User', 'User_Session', 'User_Handshake');
}
}
and then
class UserHandshakeRepositoryIntegrationTest extends AbstractJpaTest {
#Autowired UserHandshakeRepoImpl handshakeRepository;
#Test
public void testSave() {
UserHandshake handshake = handshakeRepository.save(new UserHandshake());
assertThat(handshake.getId(), is(notNullValue()));
}

How to combine many Spring test annotations in a single annotation?

I am using Spring Boot's convenient annotations on my test classes, for integration tests.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Config.class)
#IntegrationTest
#Sql({"classpath:rollback.sql", "classpath:create-tables.sql"})
#Transactional
I found it quite ugly to copy/paste this whole block on each test class, so I have created my own #MyIntegrationTest annotation
#SpringApplicationConfiguration(classes = Config.class)
#IntegrationTest
#Sql({"classpath:database-scripts/rollback.sql", "classpath:database-scripts/create-tables.sql", "classpath:database-scripts/insert-test-data.sql"})
#Transactional
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(value = RetentionPolicy.RUNTIME)
#interface MyIntegrationTest {
}
However, if I add the #RunWith(SpringJUnit4ClassRunner.class) in my new annotation, then JUnit will run with its default runner - which is not desirable.
So for now I have to use two annotations.
#RunWith(SpringJUnit4ClassRunner.class)
#MyIntegrationTest
I guess it is fine for now, but is there a way to combine these annotations, so I would be able to use a single annotation?
Meta-annotations are not the only way of code reuse. We use inheritance instead:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Config.class)
#IntegrationTest
#Sql({"classpath:rollback.sql", "classpath:create-tables.sql"})
#Transactional
public abstract class IntegrationTest {
}
public class FooTest extends IntegrationTest {
}
public class BarTest extends IntegrationTest {
}
Unlike meta-annotations, annotation inheritance from base classes is understood by both Spring and JUnit.
Ok so I've found some old discussions about this on the JUnit GitHub:
https://github.com/junit-team/junit/issues/194
https://github.com/junit-team/junit/issues/202
It is some sort of trade-off between readability and DRY-ness.
Allowing some meta-annotations could also slow down tools like IDEs.
I doesn't seem that it's on the way to be implemented any time soon, so for now I will have to keep my two annotations.

Specifying classes loading order in #ContextConfiguration in JUnit test cases

I am using junit 4.11 and spring-test-4.1.5 for writing integration test cases. I am using #ContextConfiguration annotation to load the configurations.like,
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes ={ApplicationConfig.class,SpringConfiguration.class }, initializers = {CustomProfileResolver.class, AppInitializer.class}, loader = SpringApplicationContextLoader.class)
As you can see i am using two configuration classes.
The problem i am facing is i want to load the SpringConfiguration.class before ApplicationConfig.class. I tried with reversing the order but it didn't worked. Neither #order can be specified here.
So how to order the loading of these configuration classes, is it possible ?
#ContextConfiguration and its supporting classes (e.g., MergedContextConfiguration, AnnotationConfigContextLoader, AbstractTestContextBootstrapper, etc.) are intended to retain the original ordering of #Configuration classes declared via the classes attribute of #ContextConfiguration.
Thus, if you can verifiably prove that reversing the order of your SpringConfiguration and ApplicationConfig classes does not result in SpringConfiguration being processed first, then you have found a bug in Spring.
If you have found a bug, please report it in JIRA.
Thanks,
Sam
Use #ContextHierarchy
#ContextHierarchy({
#ContextConfiguration(classes = {SpringConfiguration.class}),
#ContextConfiguration(classes = {ApplicationConfig.class})
}

Categories