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
Related
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
I have following class, which publishes the spring event.
#Component
public class ApplicationReadyEventListener {
Boolean isHit = false;
#EventListener
public void handle(final ApplicationReadyEvent applicationReadyEvent) {
applicationReadyEvent.getSpringApplication().getClass().toGenericString()));
isHit = true; // This needs to be replaced with CustomLoggerComponent
}
}
As I need to publish the event and need to check failure and success event, I have following test:
#ExtendWith({SpringExtension.class})
class ApplicationReadyEventListenerTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner();
//Success Test
#Test
void loggerShouldLogWhenApplicationIsReady() {
SpringApplciation application = new SpringApplication(ApplicationReadyEventListener.class);
application.setWebApplicationType(WebApplicationType.NONE);
final ApplicationReadyEvent event = new ApplicationReadyEvent(application, null, mock(ConfigurableApplicationContext.class));
runner.withBean(ApplicationReadyEventListener.class)
.run(context -> {
context.publishEvent(event);
final ApplicationReadyEventListener applicationStartedListener = context.getBean(ApplicationReadyEventListener.class);
MatcherAssert.assertThat(applicationStartedListener.isHit(), is(true));
});
}
//FailureTest
#Test
void shouldNotCallApplicationStarted() {
SpringApplciation application = new SpringApplication(ApplicationReadyEventListener.class);
application.setWebApplicationType(WebApplicationType.NONE);
final RuntimeException runtimeException = new RuntimeException("Some Error Occurred");
final ApplicationEvent event = new ApplicationFailedEvent(application, null, mock(ConfigurableApplicationContext.class), runtimeException);
runner.withBean(ApplicationReadyEventListener.class)
.run(context -> {
context.publishEvent(event);
final ApplicationReadyEventListener applicationStartedListener = context.getBean(ApplicationReadyEventListener.class);
MatcherAssert.assertThat(applicationStartedListener.isHit(), is(false));
});
}
}
This is working fine as of now as the class (ApplicationReadyEventListener) does not have any bean. I want to have a custom logger for this one, and instead of isHit, I would be checking the side effect of custom logger's method getting called.
However, I could not add any dependency, so I tried to isolate the problem by creating a separate application which contains the subject under test ApplicationReadyEvent and to have CustomLoggerBean created, used following one:
#Configuration
public class CustomLogMockProvider {
#Bean
public Logger logger() {
return Mockito.mock(Logger.class);
}
}
And when I write this test for the same:
#Test
void tesCustomLoggerBeanPresence() {
SpringApplciation application = new SpringApplication(CustomLogger.class);
application.setWebApplicationType(WebApplicationType.NONE);
runner.withBean(CustomLogMockProvider.class)
.run(context -> {
String[] beanNamesForType = context.getBeanNamesForType(Logger.class);
Arrays.stream(beanNamesForType).forEach(System.out::println);
});
}
Getting UnsatisfiedDependencyException for the above one.
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'applicationReadyEventListener': Unsatisfied dependency expressed through constructor parameter 0: Could not convert argument value of type [java.lang.Class] to required type [com.priti.com.common.config.CustomLogger]: Failed to convert value of type 'java.lang.Class' to required type 'com.priti.com.common.config.CustomLogger'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Class' to required type 'com.priti.com.common.config.CustomLogger': no matching editors or conversion strategy found
Any lead on this would be helpful.
I think your test setup is the problem. You're doing far to much things by hand instead of using the proper Spring tooling.
If you want to test your ApplicationReadyEventListener the test should look like this:
#ExtendWith({SpringExtension.class})
#ContextConfiguration(classes = ApplicationReadyEventListener.class)
class ApplicationReadyEventListenerTest {
#MockBean
private SpringApplication springApplicationMock;
#MockBean
private CustomLogger customLoggerMock;
#Autowired
private ApplicationEventPublisher publisher;
#Test
void name() {
publisher.publishEvent(new ApplicationReadyEvent(springApplicationMock, null, null));
verify(customLoggerMock).doSomething();
}
}
You run a Spring test by using SpringExtension.class. You mock your CustomLogger as well as the SpringApplication. Now you can publish a ApplicationReadyEvent and verify that your listener has invoked the CustomLogger.
We have a Spring 5 application using JUnit 4 as our test harness (w/ SpringRunner). We're experiencing an issue where a private helper method that's not marked with a #Test annotation is being run as a test. This happens in both IntelliJ and Maven.
The method signature is:
private Optional<TestSuiteJsonObject> createTestSuite(String name, TestType type) throws IOException, LicenseException {
And the test class itself looks like:
public class TestSuitesControllerTest extends ControllerTest
There are no annotations on either. The ControllerTest looks like:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = CompanyTestApplication.class)
#AutoConfigureMockMvc
#Ignore
public class ControllerTest {
...
}
The behavior is that these test methods are run with null arguments. We don't want them to be run at all. The rest of the methods in this class are appropriately marked with the #Test annotation.
I wondered if the fact that the word test is in the class/method name could be causing JUnit to identify it as runnable, but changing the names of both the class and method has no effect.
Adding #Ignore to the private method also has no effect!
What are we doing wrong? Did we step into an alternate dimension where test harnesses are actually testing stress responses of the engineers?
It was a silly mistake, but I'm leaving it for any future folk who find themselves in the same situation.
I had two methods with the same name. The test method:
#Test
#WithMockUser(username = "admin", roles = "ADMIN")
public void createSuite() throws Exception { ... }
And the helper method:
private static Optional<TestSuiteJsonObject> createSuite(String name, TestType type) { ... }
And we somehow glossed over this. 🤦♂️
For one of my Spring beans(say Application class), I'm fetching the value of a property(my.property.flag=true/false) from a properties file(prop.properties) using #Value annotation. That works perfectly fine.
I need to write an integration test(say ApplicationIt class) where I need to test with both the values of the property i.e. for both true and false.
In my properties file, the value of the property is set to true. Is it possible to set the value dynamically to false from my Integration test?
For Example,
prop.properties:
my.property.flag=true
Application class file:
#Component
class Application {
//This value is fetched from properties file
//the value is set to true.
#Value(${my.property.flag})
private String isTrue;
......
..........
}
Integration Test:
class ApplicationIT {
//how can I set the value of isTrue here to false?
}
You can specify test properties on the test class as follows:
#RunWith(SpringRunner.class)
#TestPropertySource(properties = {"spring.main.banner-mode=off", "my.property.flag=false"})
public class MyTest {
Since Spring has a whole hierarchy of property overrides, this works pretty well, the downside being you need separate test classes for different values. If you're using Spring Boot, there's another annotation that provides the same functionality but also has more options for configuring your test environment. Example:
#SpringBootTest(properties = {"spring.main.banner-mode=off", "my.property.flag=false"})
Again, you will need separate test classes to handle hard-coded test properties.
I was bugged with this for a while and found this neat way to override the properties. It is quite useful if you need some programmatic initialization of the application context such as registering property sources like in that case but not only. The following approach uses ContextConfiguration's initializers.
example for Spring Boot 1.5.x :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"management.port=0"})
#ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
#DirtiesContext
public abstract class AbstractIntegrationTest {
private static int REDIS_PORT = 6379;
#ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(REDIS_PORT);
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ctx) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(ctx,
"spring.redis.host=" + redis.getContainerIpAddress(),
"spring.redis.port=" + redis.getMappedPort(REDIS_PORT));
}
}
}
example for Spring Boot 2.x :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"management.port=0"})
#ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
#DirtiesContext
public abstract class AbstractIntegrationTest {
private static int REDIS_PORT = 6379;
#ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(REDIS_PORT);
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ctx) {
TestPropertyValues.of(
"spring.redis.host:" + redis.getContainerIpAddress(),
"spring.redis.port:" + redis.getMappedPort(REDIS_PORT))
.applyTo(ctx);
}
}
}
I want to mention good old reflection way. You can use spring provided utility class for it after you wired in your component:
ReflectionTestUtils.setField(component, "isTrue", true)
You can change it to any value you want in consequent tests
Preferably, use constructor injection instead of field injection:
#Component
class Application {
Application(#Value("${my.property.flag}") boolean flag) {
...
}
}
This makes using mocks or test values as simple as passing an argument.
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