How to test a component / bean in Spring Boot - java

To test a component/bean in a Spring Boot application, the testing part of the Spring Boot documentation provides much information and multiple ways :
#Test, #SpringBootTest, #WebMvcTest, #DataJpaTest and still many other ways.
Why provide so many ways ?
How decide the way to favor ?
Should I consider as integration tests my test classes annotated with Spring Boot test annotations such as #SpringBootTest, #WebMvcTest, #DataJpaTest ?
PS : I created this question because I noticed that many developers (even experienced) don't get the consequences to use an annotation rather than another.

TL-DR
write plain unit tests for components that you can straightly test without loading a Spring container (run them in local and in CI build).
write partial integration tests/slicing unit test for components that you cannot straightly test without loading a Spring container such as components related to JPA, controllers, REST clients, JDBC ... (run them in local and in CI build)
write some full integration tests (end-to-end tests) for some high-level components where it brings values (run them in CI build).
3 main ways to test a component
plain unit test (doesn't load a Spring container)
full integration test (load a Spring container with all configuration and beans)
partial integration test/ test slicing (load a Spring container with very restricted configurations and beans)
Can all components be tested in these 3 ways ?
In a general way with Spring any component can be tested in integration tests and only some kinds of components are suitable to be tested unitary(without container).
But note that with or without spring, unitary and integration tests are not opposed but complementary.
How to determine if a component can be plain tested (without spring) or only tested with Spring?
You recognize a code to test that doesn't have any dependencies from a Spring container as the component/method doesn't use Spring feature to perform its logical.
Take that FooService class :
#Service
public class FooService{
private FooRepository fooRepository;
public FooService(FooRepository fooRepository){
this.fooRepository = fooRepository;
}
public long compute(...){
List<Foo> foos = fooRepository.findAll(...);
// core logic
long result =
foos.stream()
.map(Foo::getValue)
.filter(v->...)
.count();
return result;
}
}
FooService performs some computations and logic that don't need Spring to be executed.
Indeed with or without container the compute() method contains the core logic we want to assert.
Reversely you will have difficulties to test FooRepository without Spring as Spring Boot configures for you the datasource, the JPA context, and instrument your FooRepository interface to provide to it a default implementation and multiple other things.
Same thing for testing a controller (rest or MVC).
How could a controller be bound to an endpoint without Spring? How could the controller parse the HTTP request and generate an HTTP response without Spring? It simply cannot be done.
1)Writing a plain unit test
Using Spring Boot in your application doesn't mean that you need to load the Spring container for any test class you run.
As you write a test that doesn't need any dependencies from the Spring container, you don't have to use/load Spring in the test class.
Instead of using Spring you will instantiate yourself the class to test and if needed use a mock library to isolate the instance under test from its dependencies.
That is the way to follow because it is fast and favors the isolation of the tested component.
Here how to unit-test the FooService class presented above.
You just need to mock FooRepository to be able to test the logic of FooService.
With JUnit 5 and Mockito the test class could look like :
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
#ExtendWith(MockitoExtension.class)
class FooServiceTest{
FooService fooService;
#Mock
FooRepository fooRepository;
#BeforeEach
void init{
fooService = new FooService(fooRepository);
}
#Test
void compute(){
List<Foo> fooData = ...;
Mockito.when(fooRepository.findAll(...))
.thenReturn(fooData);
long actualResult = fooService.compute(...);
long expectedResult = ...;
Assertions.assertEquals(expectedResult, actualResult);
}
}
2)Writing a full integration test
Writing an end-to-end test requires to load a container with the whole configuration and beans of the application.
To achieve that #SpringBootTest is the way :
The annotation works by creating the ApplicationContext used in your
tests through SpringApplication
You can use it in this way to test it without any mock :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
#SpringBootTest
public class FooTest {
#Autowired
Foo foo;
#Test
public void doThat(){
FooBar fooBar = foo.doThat(...);
// assertion...
}
}
But you can also mock some beans of the container if it makes sense :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
#SpringBootTest
public class FooTest {
#Autowired
Foo foo;
#MockBean
private Bar barDep;
#Test
public void doThat(){
Mockito.when(barDep.doThis()).thenReturn(...);
FooBar fooBar = foo.doThat(...);
// assertion...
}
}
Note the difference for mocking as you want to mock a plain instance of a Bar class (org.mockito.Mock annotation)and that you want to mock a Bar bean of the Spring context (org.springframework.boot.test.mock.mockito.MockBean annotation).
Full integration tests have to be executed by the CI builds
Loading a full spring context takes time. So you should be cautious with #SpringBootTest as this may make unit tests execution to be very long and generally you don't want to strongly slow down the local build on the developer's machine and the test feedback that matters to make the test writing pleasant and efficient for developers.
That's why "slow" tests are generally not executed on the developer's machines.
So you should make them integration tests (IT suffix instead of Test suffix in the naming of the test class) and make sure that these are executed only in the continuous integration builds.
But as Spring Boot acts on many things in your application (rest controllers, MVC controllers, JSON serialization/deserialization, persistence, and so for...) you could write many unit tests that are only executed on the CI builds and that is not fine either.
Having end-to-end tests executed only on the CI builds is ok but having also persistence, controllers or JSON tests executed only on the CI builds is not ok at all.
Indeed, the developer build will be fast but as drawback the tests execution in local will detect only a small part of the possible regressions...
To prevent this caveat, Spring Boot provides an intermediary way : partial integration test or the slice testing (as they call it) : the next point.
3)Writing a partial integration test focusing on a specific layer or concern thanks to slice testing
As explained in the point "Recognizing a test that can be plain tested (without spring))", some components can be tested only with a running container.
But why using #SpringBootTest that loads all beans and configurations of your application while you would need to load only a few specific configuration classes and beans to test these components?
For example why loading a full Spring JPA context (beans, configurations, in memory database, and so forth) to test the controller part?
And reversely why loading all configurations and beans associated to Spring controllers to test the JPA repository part?
Spring Boot addresses this point with the slice testing feature.
These are not as much as fast than plain unit tests (that is without container) but these are really much faster than loading a whole spring context.
So executing them on the local machine is generally very acceptable.
Each slice testing flavor loads a very restricted set of auto-configuration classes that you can modify if needed according to your requirements.
Some common slice testing features :
Auto-configured JSON Tests : #JsonTest
To test that object JSON serialization and deserialization is working
as expected, you can use the #JsonTest annotation.
Auto-configured Spring MVC Tests : #WebMvcTest
To test whether Spring MVC controllers are working as expected, use
the #WebMvcTest annotation.
Auto-configured Spring WebFlux Tests : #WebFluxTest
To test that Spring WebFlux controllers are working as expected, you
can use the #WebFluxTest annotation.
Auto-configured Data JPA Tests : #DataJpaTest
You can use the #DataJpaTest annotation to test JPA applications.
And you have still many other slice flavors that Spring Boot provides to you.
See the testing part of the documentation to get more details.
Note that if you need to define a specific set of beans to load that the built-in test slice annotations don't address, you can also create your own test slice annotation(https://spring.io/blog/2016/08/30/custom-test-slice-with-spring-boot-1-4).
4)Writing a partial integration test focusing on specific beans thanks to lazy bean initialization
Some days ago, I have encountered a case where I would test in partial integration a service bean that depends on several beans that themselves also depend on other beans.
My problem was that two deep dependency beans have to be mocked for usual reasons (http requests and a query with large data in database).
Loading all the Spring Boot context looked an overhead, so I tried to load only specific beans.
To achieve that, I annotation the test class with #SpringBootTest and I specified the classes attribute to define the configuration/beans classes to load.
After many tries I have gotten something that seemed working but I had to define an important list of beans/configurations to include.
That was really not neat nor maintainable.
So as clearer alternative, I chose to use the lazy bean initialization feature provided by Spring Boot 2.2 :
#SpringBootTest(properties="spring.main.lazy-initialization=true")
public class MyServiceTest { ...}
That has the advantage to load only beans used at runtime.
I don't think at all that using that property has to be the norm in test classes but in some specific test cases, that appears the right way.

Related

How to test Java Spring Boot application without #SpringBootApplication using JUnit?

I created a Spring Boot Java library which has a set of utilities that perform certain functions. This library can then be used across multiple applications that require its utilities. This library was designed as a Spring Boot application to allow these utilities to be injected in the application context.
Now I wish to execute a JUnit test on one of the utilities to ensure it is working correctly. However, since this application is basically a collection of utilities and not a stand-alone application, it does not have a main class or the main method annotated with #SpringBootApplication. Now, when I run the JUnit test, it comes up with an error.
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...)
Is it possible to test this Java library, or should we write the test cases only in the application that will be using this library?
I think there is some contradiction in what you say:
Created a Library...that was designed as a Spring Boot Application.
Library can be used across multiple applications that require its utilities.
If you implement "1" then there is a module with spring boot maven/gradle plugin configured to create a JAR of the application which is a library.
But if you have, say, module X that wishes to use your library, its impossible to add the dependency on your library in this module, spring boot JAR artifacts are not JARs in a Java Sense... So this won't work in both IDE and maven (I mean technically you'll have compilation errors).
Then you write something that completely makes sense: You say that the library by itself doesn't have a main class/#SpringBootApplication annotated class. From this I conclude that its not a spring boot application, but rather a spring boot starter module.
In this case you should not use #SpringBootTest annotation since it mimics the way of starting up the spring boot application (finds main class, scans the packages according to the package structure, loads the configurations and so forth). You don't need all this. Well, maybe technically you can still create a main class annotated with #SpringBootApplication and put it into src/test/java/.../ in a relevant package, but it doesn't really makes sense.
So basically you have two choices:
You can test the utilities without spring at all as if the utility is just a Java class, mock the dependency with Mockito and you're good to go. Since these tests are fast, it you be the best option.
You can run the integartion test by means of loading the spring context with all the required beans created by the application.
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {MyLibraryConfiguration.class})
public class SampleLibraryTest {
#Autowired
private SomeBeanFromTheLibrary theBean;
#Test
public void testFoo() {
....
}
}
Now although you can use component scanning (in this case you'll need slightly different annotations), in the example I've assumed that you're using java config, register all the beans of the library there and create a spring.factories that uses this Java Configuration file to create an autoconfiguration (you add a dependency on the library in module X and it loads the beans defined in the library automatically).
This #ExtendsWith (#RunWith for junit 4) has nothing to do with Spring Boot, it behaves as a "plain" spring, you can autowire beans, create mock beans, there is caching of configurations, etc.

Spring boot and Flyway: Clear database data before integration tests

I'm building a REST service using Spring boot framework (v2.0.2), where I have integration tests running on a database. I googled a lot and there is a sea of articles about cleaning database before tests, but unfortunately I find them either inefficient or a hack and not for Spring boot. Could you, please, bear with me and suggest a good way for this problem?
Ideally, I think the database should be cleared not before each test, but before some group of them, like suite or maybe each test class. One of the found suggestions looks like this:
#Autowired
protected Flyway flyway;
#Before
public void init() {
flyway.clean();
flyway.migrate();
}
which rebuilds database before each test and clearly is not efficient. Changing this to static context and using #BeforeClass does not work as Spring does not inject static fields.
Is there some nice way to reach this flyway bean from static context, to make this solution work?
Sub-question here: Flyway has a command clean, which not only clears data, but drops everything and then migrate command performs migrations again. This also seems like overhead. As the migrations are checked at startup anyway, I don't see the necessity to tear down and rebuild everything before each test group. Just clearing data would be sufficient. Would you give some advice about how this can be achieved?
To sum up, I'm looking for a standard way of removing the database data (and not tables if possible) before each group of integration tests (e.g. per class). I suppose everybody faces this task while using Spring boot, so maybe there is some nice solution considered in the framework itself.
Thank you!
You could create configuration file for your tests. It would run one time before all tests.
#Configuration
public class TestConfig {
#Bean
public FlywayMigrationStrategy clean() {
return flyway -> {
flyway.clean();
flyway.migrate();
};
}
}
This answer was useful but it didn't get me all the way there and so I thought I'd come back and add an answer in case someone else was looking to solve this same issue. The bean definition above was terrific.
There are spring profiles of which there are 5 or so possibilities. I looked at the docs and how people use them but went another route. Maven has 6 scopes, but the ones which are useful in this case are runtime and test.
As I dug into the spring profiles and the various ways one can switch between them it seemed for my situation a bit too complex. I just want my database under test to be created, scructured and populated with some data so I can test the repositories in my jpa spring boot app. I don't want to spend 4 hours setting up profiles. Not that it isn't a worthy endeavor in the long run, just that I wanted to get things moving.
When I execute spring-boot:run, I want the non-test db to be migrated but I don't want any crud data in there that I use for testing.
So, in the live app I want a virtually empty database and during tests, I want flyway to clean the db, run the versioned migrations and populate it with test data.
The answer above led me to a solution which I will probably fold into spring profiles as my project gets closer to production.
Turns out that spring-boot-test provides a #TestConfiguration annotation which you can attach to any class in the src/test/ hierarchy. I created a FlywayConfiguration class which contains the bean definition provided above:
package com.foo.fooservice;
import org.flywaydb.core.Flyway;
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
#TestConfiguration
public class FlywayMigrationConfig {
#Bean
public static FlywayMigrationStrategy cleanMigrateStrategy(){
return flyway -> {
flyway.clean();
flyway.migrate();
};
}
}
So now, if I want to use this in a test, I add another nifty annotation in the appropriate test class- #Includes, a companion to the #TestConfiguration annotation - so that I can use this configuration in the same way I might have used #BeforeClass like so:
#DataJpaTest
#Import(FlywayMigrationConfig.class)
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class AccountUserRepoTest {
#Autowired
private AccountUserRepo accountUserRepo;
#Autowired
private FlywayMigrationStrategy strategy;
This allows me to inject this flyway migration strategy on a per-test-class basis. Spring won't auto-inject your bean into every test class and you can use this configuration now just by adding the #Includes annotation to the appropriate test classes. You don't have to define the bean in every test class where you want to use it. Just use #Includes(your#TestCongiguration-annoted-class).
I happen to be using postgresSQL as opposed to H2 because I figure if I'm doing an integration test on the repository entities I might as well do it against what I will be using in production.
Also: src/main/resources has the jdbc and flyway properties set to a dev schema name and jdbc url.
src/test/resources/application.properties sets the schema name to 'test' (you can name it whatever you want).
The one drawback to this approach that you may not want is the granularity- the DB is cleaned and repopulated for every test class which you configure this way.
I personally like this because for each repository class I'm testing I'd like the data to be refreshed. I also like that if I'm working on a particular test class, having the configuration at this level of granularity means that 'run test' works out of the box. No special configuration in the IDE is required to make it work.

Use one spring boot context through all SpringBootTests

I want to be able to cache application context through different classes with tests using junit.
Test classes are declared this way:
#SpringBootTest
#RunWith(SpringRunner.class)
public class SomeIntegrationTest {
}
I saw this question Reuse spring application context across junit test classes but in this case I don't use any xml and I want to start context completely, not just few beans from it, so #SpringBootTest is more suitable than #ContextConfiguration, if I got it right.
Ruslan, so your question is on how to reuse the Spring Boot Context for a JUnit Suite, right?
Then, it's almost out-of-the-box provided, you just need to annotate each unit test with the #SpringBootTest annotation.
Also make sure that your main #SpringBootApplication class is loading all the necessary #Configuration classes, this process will be automatically done if the #SpringBootApplication is on the root package above all configuration class and with the inherited #ComponentScan will load up all of them.
From the Spring Boot Testing documentation:
Spring Boot provides a #SpringBootTest annotation which can be used as an alternative to the standard spring-test #ContextConfiguration annotation when you need Spring Boot features. The annotation works by creating the ApplicationContext used in your tests via SpringApplication.
The Spring TestContext framework stores application contexts in a static cache. This means that the context is literally stored in a static variable. In other words, if tests execute in separate processes the static cache will be cleared between each test execution, and this will effectively disable the caching mechanism.
To benefit from the caching mechanism, all tests must run within the same process or test suite. This can be achieved by executing all tests as a group within an IDE
From the Spring Testing documentation:
By default, once loaded, the configured ApplicationContext is reused for each test. Thus the setup cost is incurred only once per test suite, and subsequent test execution is much faster. In this context, the term test suite means all tests run in the same JVM
Check this urls:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/integration-testing.html#testcontext-ctx-management-caching
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/integration-testing.html#testing-ctx-management
Main Takeaways:
Annotate each unit test with #SpringBootTest
Load all beans and necessary configuration classes in your main #SpringBootApplication class
IMPORTANT: Run a JUnit Suite, not a single JUnit test. Execute all tests as a group within your IDE.
#SpringBootTest provides ApplicatonContext caching and sharing in Junit Test Cases naturally.
While some cases may be exceptions. For example, using #MockBean or #SpyBean in a test class will cause the ApplicatonContext cache failure.
While Spring’s test framework caches application contexts between
tests and reuses a context for tests sharing the same configuration,
the use of #MockBean or #SpyBean influences the cache key, which will
most likely increase the number of contexts.
https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/html/boot-features-testing.html
And this kind of issue is still not solved in the latest SpringBoot release.
https://github.com/spring-projects/spring-boot/issues/21099
https://github.com/spring-projects/spring-boot/issues/7174

Using Junit with spring

I'm trying to set up a junit with spring and I'm trying to use spring's dependency injection to populate the test class. I'm wondering if this is something I should even be attempting? I think what I'm seeing is spring is instantiating the test class and performing the DI but then JUnit is creating it's own instance that hasn't had DI performed and the test is failing. I'm using JUnit 4.x and spring 3.1.1.
You can use spring to inject dependencies into your tests, thus making it an integration test. Annotate like this
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
#ContextConfiguration(locations = "/applicationContext-TEST.xml")
public class MyTest {}
But it can be preferable to just test your spring managed classes as pojo's and use mock objects where appropriate.
For example a lot of controller methods have a Model injected at runtime by Spring. However to unit test them I just pass in an HashMap instance. And my service layer classes I can pass in a mocked dao, which is easy because I designed to an interface and use setter injection...
With jUnit, each test should be isolated with no dependency outside of test coverage. There are several test frameworks available that provide mock bean instantiation in Spring.
There is an excellent Martin Fowler article on Stubs and Mocks to begin with.
Mockito in combination with PowerMock, can help you test spring components, services and controllers.
Mockito Intro: https://code.google.com/p/mockito/
PowerMock Intro: http://code.google.com/p/powermock/
I understand this will take up time to research, learn and implement, but this is very beneficial for writing jUnit tests with Dependency Injected beans.

advice on integration testing my dao layer

I have a spring mvc application, and I am using jdbctemplate for my database Dao objects.
How should I go about integration testing?
Where should I put my integration testing files, is this layout correct:
/src/main/test/integration/...
or
/src/main/integration/...
How will this work, for my test cases, I should have a base class or some code in my setup that will run once before starting my testing where it loads the spring configuration/application context.
How should I do this?
If You are using Maven, tests should go to src/test/java. If You're not, it seems like a reasonable place anyway.
To set up spring context You should use #RunWith(SpringJUnit4ClassRunner.class) together with #ContextConfiguration, no initialization code necessary.

Categories

Resources