Spring Boot Integration Testing - Mocking #Service before Application Context Starts? - java

I have to create a integration test for a microservice X which downloads, processes and importing csv files from external sftp servers. The whole process is started by a spring boot scheduler task which starts a spring batch job for processing and importing the data. The import process is done by the spring batch writer, which is a restTemplate Repository (so it calls post requests to another microservice Y).
I already managed to mock the sftp server, putting a test file on it and the current integration test is downloading the file. (https://github.com/stefanbirkner/fake-sftp-server-rule/)
My problem is, that the task will be scheduled immediately when the application context starts so there is no trigger like a api call. To get the whole integration test working i have to mock the part where the external microservice Y is called through a restTemplate call. This repository is called in the spring batch writer and this repository is created by a repositoryFactory which is a #Service. The repositoryFactory is injected in the spring batch configuration class.
I already tried to use the #MockBean annotation in the test class as well as in a separate test configuration where i am mocking the create() function of the factory to deliver a repository mock. But at some point it does not work and it delivers still the original object which leads to interupt the import job.
I also tried to use the WireMock library, but also in this case it does not catched any api calls and at some point leads to interrupt the sftp socket. (?)
I hope someone could help me out.
The current test:
#NoArgsConstructor
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(classes = {JsonHalConfig.class})
#TestPropertySource(locations = "classpath:application-test.properties")
#TestMethodOrder(MethodOrderer.MethodName.class)
public class ImportIT {
#ClassRule
public static final FakeSftpServerRule sftpServer = new FakeSftpServerRule();
private static final String PASSWORD = "password";
private static final String USER = "username";
private static final int PORT = 14022;
#BeforeClass
public static void beforeClass() throws IOException {
URL resource = getTestResource();
if (resource != null) {
sftpServer.setPort(PORT).addUser(USER, PASSWORD);
sftpServer.createDirectories("/home/username/it-space/IMPORT", "/home/username/it-space/EXPORT");
sftpServer.putFile("/home/username/it-space/IMPORT/INBOX/testFile.csv",
resource.openStream());
} else {
throw new IOException("Failed to get test resources");
}
}
private static URL getTestResource() {
return ImportIT.class.getClassLoader().getResource("testFile.csv");
}
#Test
public void test_A_() throws IOException, RepositoryException {
assertTrue(true);
}
}
I tried following configuration classes
(included in #ContextConfiguration)
#Configuration/#TestConfiguration
public class RepositoryTestConfig {
#Bean
#Primary
public IRepositoryFactory repositoryFactory() {
IRepositoryFactory repositoryFactory = mock(IRepositoryFactory.class);
IRepository repository = mock(IRepository.class);
when(repositoryFactory.create(anyString())).thenReturn(repository);
return repositoryFactory;
}
}
(as static class in the test class)
#TestConfiguration/#Configuration
public static class RepositoryTestConfig {
#MockBean
private IRepositoryFactory repositoryFactory;
#PostConstruct
public void initMock(){
IRepository repository = mock(IRepository.class);
Mockito.when(repositoryFactory.create(anyString())).thenReturn(
repository
);
}
}
UPDATE 27.08.2021
I have a RestConfig #Component where a new RestTemplateBuilder is created. I tried to #MockBean this component to deliver a RestTemplateBuilder Mock and injected a MockRestServiceServer object to catch outgoing api calls. But unfortunately it does not work as aspected. Am i missing something? I also tried to create a "TestRestController" to trigger the scheduling of the task but it never delivers the mock...

I normally use #MockBean directly inside my test classes and inject the dedicated (but mocked) repository directly there and not create it inside the test configuration. I also add the test configuration class by #ContextConfiguration so it is loaded in current test context.
Inside my tests I am just using mockito the standard way and prepare the mocked parts as wanted for the dedicated test method.
Here an example snippet:
// ... do some imports ...
#RunWith(SpringRunner.class)
#ContextConfiguration(classes= {XYZSomeWantedClazz.class, DemoXYZMockTest.SimpleTestConfiguration.class})
#ActiveProfiles({Profiles.TEST})
public class DemoXYZMockTest {
//...
#MockBean
private DemoRepository mockedDemoRepository;
// ...
#Test
public void testMethodName() throws Exception{
/* prepare */
List<WantedEntityClazz> list = new ArrayList<>();
// add your wanted data to your list
// apply to mockito:
when(mockedDemoRepository.findAll()).thenReturn(list);
/* execute */
// ... execute the part you want to test...
/* test */
// ... test the results after execution ...
}
#TestConfiguration
#Profile(Profiles.TEST)
#EnableAutoConfiguration
public static class SimpleTestConfiguration{
// .. do stuff if necessary or just keep it empty
}
}
For a complete (old Junit4) working test example please take a look at:
https://github.com/mercedes-benz/sechub/blob/3f176a8f4c00b7e8577c9e3bea847ecfc91974c3/sechub-administration/src/test/java/com/daimler/sechub/domain/administration/signup/SignupAdministrationRestControllerMockTest.java

Related

Java writing Spring Boot Unit Tests

I was wondering if I wrote this test in a proper way to mock a real situation. Can you please provide any feedback?
I'm using Mockito in a Spring Boot environment. I'm new to Mockito and other mocking techniques, but I want to know if I'm on the right path or not.
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
class FileServiceImplTest {
#Mock
private UserPrincipal userPrincipal;
#Mock
private User user;
#Mock
private FileService fileService;
#BeforeEach
void initialize() {
user = new User("testUser", "test#email.com", "testPassword");
user.setId(1L);
user.setRoles(List.of(Role.User));
userPrincipal = UserPrincipal.create(user);
}
#Test
void usedMockUpsShouldNotBeNull() {
assertAll("All mock instaces of classes should not be null",
() -> assertNotNull(fileService),
() -> assertNotNull(userPrincipal),
() -> assertNotNull(user)
);
}
#Test
void collectionOfFilesShouldChangeAfterNewFileIsAdded_Mockito() throws ExecutionException, InterruptedException {
Image image = createImageFile();
List<? super File> files = createFilesCollection();
doReturn(files).when(fileService).getAll(userPrincipal);
int initialSize = fileService.getAll(userPrincipal).size();
fileService.save(image);
doReturn(files.add(image)).when(fileService).save(image);
int newSize = fileService.getAll(userPrincipal).size();
assertNotEquals(newSize, initialSize);
Mockito.verify(fileService, atLeast(2)).getAll(userPrincipal);
Mockito.verify(fileService).save(image);
}
}
I am sorry that you are in the wrong path.
If FileService is just an interface, you do not need to test it.
If FileService is an implementation, you should create an actual instance to test it but not a mock.
The mock is only useful for the direct dependencies of the class that you are testing (i.e FileSerivice) such that you can easily stub the result when calling methods on these dependencies and verify if the class you are testing interacts with them correctly. (i.e. call the correct methods with the correct parameters etc.)
As I cannot see FileSerivice 's source codes , but if UserPrincipal and User are not its dependencies , it does not make sense to mock them.
Also as mentioned by other in the comment, as you are not doing the integration testing with some Spring framework stuff, you should simply rewrite your test as a plain Mockito test which is simpler and run faster :
#ExtendWith(MockitoExtension.class)
public class FileServiceImplTest {
}

Junit 5 with test application.properties

I am learning Junit 5 and test cases.
I am using spring boot version '2.2.6.RELEASE and JUnit 5,
in my application, I have a method that processes based on the boolean flag from property file.
\src\main\resources\application.properties
#data base connection properties
spring.app.datasource.url=jdbc:mysql://localhost:3306/student_db
spring.app.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.app.datasource.username=root
spring.datasource.password=root
spring.app.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
#additional properties
spring.property.name=shrikant
spring.property.enable=false
database connection properties are used to create the database connection
Datasource.java
#Value("${spring.app.datasource.url}")
private String url;
#Value("${spring.app.datasource.driver-class-name}")
private String className;
#Value("${spring.app.datasource.username}")
private String userName;
#Value("${spring.datasource.password}")
private String password;
#Value("${spring.app.jpa.properties.hibernate.dialect}")
private String dialect;
controller class
#RestController
public class Controller {
#Value("${spring.property.name}")
private String name;
#Value("${spring.property.enable}")
private boolean status;
public void validateObject(String surName) {
if (status) { # if this flag is true then only process
System.out.println("name= " + name);
System.out.println("surName= " + surName);
}
}
ControllerTest.java
#SpringBootTest
class ControllerTest {
#Autowired
private Controller controller;
#Test
void show() {
controller.validateObject("sharma");
}
by default the flag is false, so every time test case runs it never processes the object.
so I tried to create aplication.properties in the test folder
\src\test\resources\application.properties
spring.property.name=vishal
spring.property.enable=true
but now it's giving me an error that
Could not resolve placeholder 'spring.app.datasource.url'
but I don't want to provide DB connection URL, I am not connecting to the database while testing.
Q1 - how to change the value of properties file for test case only.
Q2 - is it mandatory to provide all the keys of \src\main\resources\application.properties is \src\test\resources\application.properties?
I am new in test case, so little explained answers would be welcomed.
Update:-
I found that
#SpringBootTest
#TestPropertySource(properties = {"spring.property.name=vishal", " spring.property.status=true"})
class ControllerTest {
will solve the issue temporarily, by providing keys along with values, but I have a lot of keys, which cannot be provided in such a way.
If you use #SpringBootTest then your test will load the whole Spring context. This means it will create all your beans and try to wire them together. If you inject property values to your beans, you have to specify them all for such tests as otherwise, you won't be able to boot the application.
What might help you in such a situation is to use test annotations like #WebMvcTest or #DataJpaTest to focus on testing just slices of your application. Using #WebMvcTest you'll get an application context just containing controllers and everything related to your web layer. Other beans (like service classes) can be mocked with #MockedBean.
Next, for testing business logic in service classes try not to use #SpringBootTest and rather rely on plain JUnit 5 and Mockito to verify your code.
You still might want to have some integration tests that use #SpringBootTest to make sure everything is working together. In such case, you can hard code any static property inside application.properties in src/test/resources/ or using the annotation like you already did: #TestPropertySource(properties = {"spring.property.name=vishal", " spring.property.status=true"}).
When it comes to providing a database, you can either configure an embedded database (which I would try to avoid) or use the same database as in production. Testcontainers helps you a lot when it comes to providing external infrastructure for your tests like a database.
An example setup with Spring Boot >= 2.2.6 and JUnit might look like the following:
#Testcontainers
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationIT {
#Container
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
.withPassword("password")
.withUsername("username");
#DynamicPropertySource
static void postgresqlProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
}
#Test
public void contextLoads() {
}
}
For Junit5, annotate your test class with path to application.properties in your src/main/resources:
#ExtendWith(SpringExtension.class)
#TestPropertySource("classpath:application.properties")
public class MyTest {
#Value("${property.name}")
private int myProperty;
tests...
}
property gets loaded

How to test a Spring REST consumer with JUnit

I'm creating a Java Spring based microservice application that communicates using REST endpoints.
The app itself is so far has a simple structure: UI <-> DBLayer. The UI module is a api consumer and DBLayer is an api provider.
That being said I would like to test if my UI makes the correct REST calls using JUnit and/or Mockito. To be more specific, say I have a service class like this:
#Service
public class AuthorityService {
#Autowired
private RestTemplate restTemplate;
public Authority getAuthority(String authorityName) {
Authority authority =
restTemplate.getForObject(
"http://localhost:8080/authorities/" + authorityName,
Authority.class);
return authority;
}
}
In order to test this service method I would like to somehow verify that exactly this endpoint was called. Is there a way to wrap the service method and somehow assert a rest GET/POST/PUT etc. calls being made?
The desired test class should look something like this:
public class AuthorityServiceTest {
private AuthorityService authorityService = new AuthorityService();
#Test
public void getAuthorityTest(){
Assert.assertHttpGETCallMade(
authorityService.getAuthority("test"),
"http://localhost:8080/authorities/test");
}
}
You can use Mockito to inject the template, then verify the call.
#ExtendWith(MockitoExtension.class) // RunWith(MockitoJUnitRunner.class) for JUnit 4
public class AuthorityServiceTest {
#InjectMocks
private AuthorityService sut;
#Mock RestTemplate restTemplate;
#Test
public void getAuthorityTest(){
// mock rest call
Authority auth = mock(Authority.class);
when(restTemplate.getForObject(any(String.class), any(Class.class)).thenReturn(auth);
Authority result = sut.getAuthority("test");
// verify mock result was returned
assertSame(auth, result);
// verify call to rest template was performed
verify(restTemplate).getForObject(
"http://localhost:8080/authorities/test",
Authority.class);
}
}

Why is my Integration Test trying to setup a new jetty instance?

I have a project with 3 integration tests classes: A, B and C.
I made a change in the code, and as part of those changes I added a #MockBean to test class A.
Here is a class that is extended by every Integration Test class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApplication.class, webEnvironment = RANDOM_PORT)
#TestPropertySource(locations = "classpath:application-test.yml")
#ActiveProfiles(profiles = {"default", "test"})
public abstract class IntegrationTest {
#Value("${local.server.port}")
private int serverPort;
#Autowired
private ObjectMapper objectMapper;
#Before
public void setUpIntegrationTest() {
RestAssured.port = serverPort;
RestAssured.config = RestAssuredConfig.config()
.logConfig(LogConfig.logConfig()
.enableLoggingOfRequestAndResponseIfValidationFails()
.enablePrettyPrinting(true))
.objectMapperConfig(objectMapperConfig()
.jackson2ObjectMapperFactory((cls, charset) -> objectMapper)
.defaultObjectMapperType(ObjectMapperType.JACKSON_2))
.jsonConfig(jsonConfig().numberReturnType(BIG_DECIMAL))
.redirect(new RedirectConfig().followRedirects(false));
}
}
Now for a concrete test class:
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doNothing;
public class TestClassA extends IntegrationTest {
#MockBean
private SomeBean foo;
#Before
#Override
public void setUpIntegrationTest() {
super.setUpIntegrationTest();
doNothing().when(foo).fooMethod(any(SomeClass.class), any(SomeOtherClass.class));
}
#Test
public void testCaseX() {
given()
.body("{\"foo\": \"bar\"}")
.when()
.post("/some/path/")
.then()
.statusCode(OK.value());
}
}
I have tried to run tests in three different ways:
Run only test class A, with the mocked bean. All tests pass.
Build the project which runs all test classes. Test classes B and C pass, but A fails during application context loading while trying to start a jetty instance and fails because the address is already in use.
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.github.azagniotov.stubby4j.server.StubbyManager]: Factory method 'stubby' threw exception; nested exception is java.net.BindException: Address already in use
Caused by: java.net.BindException: Address already in use
Remove the mocked bean, and build the project. Test classes B and C pass. Test class A successfully loads the application context, but some tests fail (due to the missing behaviour given by the mock).
Jetty is setup as part of Stubby4j and it is instantiated as a configuration bean in the following way:
#Configuration
public class StubbyConfig {
#Bean
public StubbyManager stubby(final ResourceLoader resourceLoader) throws Exception {
Resource stubbyFile = resourceLoader.getResource("classpath:stubs/stubby.yml");
if (stubbyFile.exists()) {
Map<String, String> stubbyConfig = Maps.newHashMap();
stubbyConfig.put("disable_admin_portal", null);
stubbyConfig.put("disable_ssl", null);
File configFile = stubbyFile.getFile();
Future<List<StubHttpLifecycle>> stubLoadComputation =
ConcurrentUtils.constantFuture(new YAMLParser().parse(configFile.getParent(), configFile));
StubbyManager stubbyManager = new StubbyManagerFactory()
.construct(configFile, stubbyConfig, stubLoadComputation);
stubbyManager.startJetty();
return stubbyManager;
} else {
throw new FileNotFoundException("Could not load stubby.yml");
}
}
}
I did some debugging in two different ways, putting a break point in the line stubbyManager.startJetty();:
Running just test class A. Execution stopped in the break point only once.
Running test class A with some other test class (for example, B). Execution stopped only once for B, but twice for A. The second time it failed with the aforementioned error.
Again, if I remove the mocked bean and run multiple test classes the execution only stops at that line once per test class.
My question, obviously, is: why does the MockedBean annotation cause this behaviour, and how can I avoid it?
Thanks in advance.
Current project setup:
Spring Boot version 1.4.2.RELEASE
Stubby4j version 4.0.4
I just start Jetty when not already done with:
if(!stubbyManager.statuses().contains(""Stubs portal configured at")
stubbyManager.startJetty();
else
stubbyManager.joinJetty();
Let me know if you find a better solution
You could use Spring's SocketUtils class to find available TCP port, which then you could pass into your stubbyConfig map:
...
static final int PORT_RANGE_MIN = 2048;
static final int PORT_RANGE_MAX = 65535;
...
...
public StubbyConfig() {
this.stubPort = SocketUtils.findAvailableTcpPort(PORT_RANGE_MIN, PORT_RANGE_MAX);
}
...
#Bean
public StubbyManager stubby(final ResourceLoader resourceLoader) throws Exception {
...
if (stubbyFile.exists()) {
...
stubbyConfig.put("stubs", String.valueOf(stubsPort));
...
}
}
...
public int getStubsPort() {
return this.stubPort;
}
Then, because you have the port getter on your StubbyConfig class, you could pass it to the RestAssured.port when running the integration test

Mocking a class that Verticle depends on

I am playing with the Vert.x 3 framework/library.
I have written a simple Verticle that has object dependencies managed via Spring IoC.
Here is the Verticle snippet
public class BookmarksVerticle extends AbstractVerticle {
private static Logger log = LoggerFactory.getLogger(BookmarksVerticle.class);
#Resource
private BookmarkDao bookmarksDao;
Here is the Spring configuration snippet
#Bean
public BookmarkDao bookmarksDao() {
...
}
#Bean
public BookmarksVerticle bookmarkVerticle() {
return new BookmarksVerticle();
}
That is all working great. So wanted to write up some tests.
I am using the vertx-unit testing and was trying to mock the DAO
Here is what I have
#RunWith(VertxUnitRunner.class)
public class BookmarksVerticleTest {
int port = 8888;
private Vertx vertx;
#Mock(name = "BookmarkDao")
BookmarkDao mockDao;
#InjectMocks
BookmarksVerticle bmVerticle;
#Before
public void init(TestContext context) {
MockitoAnnotations.initMocks(this);
vertx = Vertx.vertx();
DeploymentOptions options = new DeploymentOptions().setConfig(new JsonObject().put("http.port", port));
vertx.deployVerticle(bmVerticle, options, context.asyncAssertSuccess());
}
However when I run the test I get NPE
SEVERE: NULL
java.lang.NullPointerException
at vertx.pragprog.bookmarks.BookmarksVerticle.asynchRetrieveBookmark(BookmarksVerticle.java:169)
at vertx.pragprog.bookmarks.BookmarksVerticle.lambda$1(BookmarksVerticle.java:88)
at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$14(ContextImpl.java:279)
at io.vertx.core.impl.OrderedExecutorFactory$OrderedExecutor.lambda$new$161(OrderedExecutorFactory.java:91)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
The line that triggers the exception is where I access the DAO
Bookmark bm = bookmarksDao.getBookmark(id);
The mockDao is not being injected into the Verticle.
Any ideas on why this might be the case?
UPDATE:
Tried removing the Mockito automatically creating classes by adding a setter method for the DAO on BookmarksVerticle and then changed the setup method in the unit test as follows:
#Before
public void setUp(TestContext context) {
log.info("setting up...");
//MockitoAnnotations.initMocks(this);
mockDao = mock(BookmarkDao.class);
bmVerticle = new BookmarksVerticle();
bmVerticle.setBookmarkDao(mockDao);
vertx = Vertx.vertx();
DeploymentOptions options = new DeploymentOptions().setConfig(new JsonObject().put("http.port", port));
vertx.deployVerticle(bmVerticle, options, context.asyncAssertSuccess());
}
Even with this approach I am still getting NPE
UPDATE 2
I removed vertx and the VertxUnitRunner from the mix by testing a method on BookmarksVerticle that did not have any dependencies on vertx but used the DAO class.
public class BookmarksServiceTest {
#Mock(name = "BookmarkDao")
BookmarkDao mockDao;
#InjectMocks
BookmarksVerticle bmVerticle;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void test_retrieveBookmark() {
String id = "1";
when(mockDao.getBookmark(Matchers.anyString())).thenReturn(new Bookmark(id, "url", "Vert.x"));
Bookmark bm = bmVerticle.retrieveBookmark(id);
assertNotNull(bm);
assertEquals(id, bm.getBookmarkId());
assertEquals("Vert.x", bm.getBookmarkTitle());
}
}
This works great! It seems that the VertxUnitRunner may be interfering with Mockito in some way.
Thanks
Good news is Mockito works with VertxUnitRunner!
It turns out using Maven on the command line and from Eclipse was messing me up. Once I switched out the embedded maven to use the same maven install as I was using from the command line things started working.
Here are the details from another answer:
Successful build in Maven still showing errors in Eclipse
Does BookmarksVerticle have non-default constructors?
The documentation for #InjectMocks states that field injection will not happen in that case.
See here: http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/InjectMocks.html

Categories

Resources