Minimizing classes to load for JUnit testing - java

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() {
(...)
}
}

Related

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

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;
...
}

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" />

How to manage spring hierarchy during unit/integration tests?

I am working on a Spring-enabled embedded Tomcat application using annotation-based configuration. The application uses Spring MVC Controllers for its REST endpoints. As a separation of concerns, and to avoid having duplicate beans in separate contexts, the parent context contains all beans that are not REST endpoints, and the Spring Web MVC context contains all beans that are REST endpoints.
I want to write new and refactor old integration tests for these endpoints that are representative of the structure of the app. There are existing test classes like so:
import com.stuff.web.MyEndpoint;
#Configuration
#ComponentScan(basePackages = {"com.stuff"})
public class SpringConfig { ... }
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {SpringConfig.class})
public class TestMyEndpoint {
#Autowired
private MyEndpoint myEndpoint;
private MockMvc mockMvc;
#Before
public void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(myEndpoint)
.build();
}
#Test
public void testMyEndpoint() throws Exception {
mockMvc.perform(get("/myendpoint")
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isOk())
.andReturn();
}
}
The problem is that the context that I am using for this test now has every bean loaded, whereas I would like to ensure that there are not non-REST beans loaded that call into RestController beans during the execution of the tests.
Adding something like
#Configuration
#ComponentScan(basePackages = {"com.stuff"},
excludeFilters = {
#Filter(type = FilterType.REGEX, pattern = "com.stuff.web.*")})
public class SpringConfig { ... }
Would ensure the kind of separation I'm going for, but then I don't have access to the com.stuff.web.MyEndpoint class that I'm trying to test.
Am I missing something easy? Let me know if I'm explaining the situation clearly.
The kind of separation you're describing (mvc vs non-mvc) made sense 10 years ago, not anymore. Separate your code by functionality/design patterns (web/service/repository etc), and have #Configuration classes specific to that layer. The Spring stereotype annotations are good enough hint how your app should be broken up. Then, put your tests in the same package as your target code, and mock/override any dependencies.
It doesn't appear you're using Spring Boot (you really should) but they have a great section in the docs for testing "slices" of your application.
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-tests

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()));
}

UnitTests and Spring - create new beans?

I have an application where I use Spring (annotations, not xml), and I need to load the beans in my unit tests. I have the AppConfig class from my code which I want to use, but with a different datasource (one I define in the test folder). This is because I want to use an in memory DB in my tests, and not the real DB.
Here's how I try to run the AppConfig class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {App.class, AppConfig.class})
public class DAOManagerTest {
//All code goes here
#AutoWired
UserDAO userDAO;
#Test
public void testGet() {
List<User> test = userDAO.selectAll();
for (User u: test) {
u.toString();
}
}
}
This doesn't entirely work, as it fails on creating a bean inside the UserDAO class. I guess I need some tutorial / guide on how to deal with spring in unit tests. Should I define new beans in my test folder, or is it possible to user the spring class from my code? Also, is it possible to define a seperate datasource for the tests?
Yes. For example, if you define some beans in DAOManagerTest, using #Primary if necessary, and add DAOManagerTest.class to #ContextConfiguration.
There are so many other ways of arranging it though, like using profiles or mocks, etc.

Categories