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.
Related
I have a class that uses an autowired properties object. These properties have some configuration required in order for my communications to work properly. In my unit tests I wrote a scenario in which communication should fail, by overriding the properties object in the class constructor as follows:
public class TokenRetriever{
#Autowired
private TokenRepository repository;
#Autowired
private Properties properties;
//custom constructor for me to override the properties
public TokenRetriever(Properties properties){
this.properties = properties;
}
private Token retrieveToken() {
Token token = null;
try {
//communication to an endpoint using properties
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return token;
}
public Token getAccessToken() throws NullAccessToken {
Token token;
token = repository.findTop1ByExpiresAtGreaterThanOrderByExpiresAtDesc(LocalDateTime.now());
if (token == null) token = this.retrieveToken();
if (token == null) throw new NullAccessToken("Could not retrieve any tokens");
return token;
}
}
And this is my unit test:
#Test
void ShouldNotRetrieveAToken() {
//this is the property i'm changing in order to force a failure
properties.setClientId("dummy");
tokenRetriever = new TokenRetriever(properties);
Exception exception = assertThrows(NullAccessToken.class,
() ->
tokenRetriever.getAccessToken()
);
String expectedMessage = "Could not retrieve any tokens";
String actualMessage = exception.getMessage();
assertTrue(actualMessage.contains(expectedMessage));
}
Which works just fine when I run the unit test. However, when I build the project this fails because the error is not thrown. I assume this is because the overriding is not working. I'm new to spring boot and junits, so this probably has to do with spring lifecycles. How can I accomplish the properties overide in order for my junit to pass?
Constructor injection Does injection only when the object create.
if you want create another object with different property object you must use Setter-based dependency injection.
there is setter-based injection documentation https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-setter-injection
You are mixing constructor and field injection.
It's recommended to use constructor injection where possible. You also will not need an annotation.
private final TokenRepository repository;
private final Properties properties;
public TokenRetriever(TokenRepository repository, Properties properties){
this.repository = repository;
this.properties = properties;
}
I guess this is a junit and Logback problem. In my project, Logging is done through slf4j. The logging implementation is Logback.
So I have a class:
#Component
#Slf4j
public class A {
private final ObjectMapper objectMapper = new ObjectMapper();
private static final String DEFAULT_REPLY = "just continue...";
public String doSomething(Object value) {
try {
return objectMapper.methodAbc(value);
} catch (JPException e) {
log.error("Exception while processing value", e);
return DEFAULT_REPLY;
}
}
}
and its test class
#RunWith(MockitoJUnitRunner.class)
public class ATest {
#Before
public void init() {
processor = new A();
}
#Test
public void test() throws Exception {
ObjectMapper mockMapper = mock(ObjectMapper.class);
JP mockJp = mock(JP.class);
Exception thrownException = new JPException(mockJp, null);
when(mockMapper.methodAbc(any())).thenThrow(thrownException);
String result = processor.doSomething("abc");
assertTrue(result.equals("just continue..."));
}
}
I don't have any problem with the test itself. Just as you can see, in the test, the JPException will be printing out on the log, because it's intentionally thrown.
When I debug through logs, there're too many this kind of expected exceptions, I'm just wondering is there a way to remove them from logs? And of course still print other exceptions which is not expected.
Logback has the functionality to support filters and evaluations based on certain logic.
This link is probably what you could be looking for :
https://logback.qos.ch/manual/layouts.html#Evaluators
You can configure your logback to do or not do certain action if it is an instance of any exceptio - in your case JPException
Try this:
Create a mock for the log.
Inject the mock into the class being tested.
Assert that the error method on the mocked log object was called.
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
I am using mean bean 'http://meanbean.sourceforge.net' to validate/test my beans. It works fine for most of the beans. But when a bean has arrays in it, it is failing with following error.
SEVERE: getFactory: Failed to find suitable Factory for property=[hostNames] of type=[class [I]. Please register a custom Factory. Throw NoSuchFactoryException.
org.meanbean.factories.ObjectCreationException: Failed to instantiate object of type [[I] due to NoSuchMethodException.
Following is my sample code.
public class Machine {
private String[] hostNames;
public String[] getHostNames() {
return hostNames;
}
public void setHostNames(String[] hostNames) {
this.hostNames = hostNames;
}
}
import org.junit.Test;
import org.meanbean.test.BeanTester;
public class TestBeanUtil {
#Test
public void test1(){
new BeanTester().testBean(Machine.class);
}
}
Any help on how to get rid of this error. I found one way by ignoring specific fields like below.
Configuration configuration = new ConfigurationBuilder().ignoreProperty("hostNames").build();
new BeanTester().testBean(Machine.class, configuration);
But My concern is is there any way that i can test without ignoring specific proper (or) ignore all the arrays in one shot ?
You can create a custom factory for your field:
class HostNamesFactory implements Factory<String[]> {
#Override
public String[] create() {
return new String[] {"host1", "host2", "host3"};
}
}
Then use this factory when you create your custom configuration and pass it to the bean tester:
Configuration configuration = new ConfigurationBuilder().overrideFactory("hostNames", new HostNamesFactory()).build();
new BeanTester().testBean(Machine.class, configuration);
I am agree this is not the perfect solution but at least the property getter and setter will be tested.
Maybe too late, but you can include in the AbstractJavaBeanTest the factories of each property that cannot be created.
Here is a sample when you need a factory for LocalDateTime and UUID
#Test
public void getterAndSetterCorrectness() throws Exception
{
final BeanTester beanTester = new BeanTester();
beanTester.getFactoryCollection().addFactory(LocalDateTime.class,
new LocalDateTimeFactory());
beanTester.getFactoryCollection().addFactory(UUID.class,
new ExecutionUUIDFactory());
beanTester.testBean(getBeanInstance().getClass());
}
Then you just define the custom factories that you want (in this example)
/**
* Concrete Factory that creates a LocalDateTime.
*/
class LocalDateTimeFactory implements Factory<LocalDateTime>
{
#Override
public LocalDateTime create()
{
return LocalDateTime.now();
}
}
/**
* Concrete Factory that creates a UUID.
*/
class ExecutionUUIDFactory implements Factory<UUID>
{
#Override
public UUID create()
{
return UUID.randomUUID();
}
}
How can I check, if ${service.property} is not an empty string and if so, throw some kind of readable exception? It must happen during Bean creation.
#Component
public class Service {
#Value("${service.property}")
private String property;
}
I am looking for the easiest way (least written code). Would be great if by using annotations.
My current solution is to perform "handwritten" validation inside setter for the property, but is a little too much code for such easy thing.
Hint: I looked for some way to use the SpEL, since I use it already inside #Value, but as far I have found out, it would not be that easy/clean. But could have overlooked something.
Clarification: Expected behaviour is, that the application will not start up. The goal is to assure, that all properties are set, and especially, that string properties are not empty. Error should say clearily, what is missing. I don't want to set any defaults! User must set it all.
You can use the component as a property placeholder itself. And then you may use any validation that you want.
#Component
#Validated
#PropertySource("classpath:my.properties")
#ConfigurationProperties(prefix = "my")
public class MyService {
#NotBlank
private String username;
public void setUsername(String username) {
this.username = username;
}
...
}
And your my.properties file will look like this:
my.username=felipe
What you have there will work. If you don't include the property in your properties file you will receive a org.springframework.beans.factory.BeanCreationException exception on server start up.
Apr 22, 2015 9:47:37 AM org.apache.catalina.core.ApplicationContext log
SEVERE: StandardWrapper.Throwable
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.lang.String com.util.Service.property; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'service.property' in string value "${service.property}"
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:306)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1146)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
The alternative would be to use an initProperty to handle or set the value, here is where you could throw some kind of readable exception.
#Component
public class Service {
private String property;
#Autowired
public void initProperty(#Value("${service.property}") String property) {
if(property == null) {
// Error handling here
}
}
}
It really depends if you want the your application to start up regardless if the property is set and if not, throw a readable exception to the log or console then set it with a default value or if you want the error to be thrown at server start-up and bean creation.
I guess the third option would be to just set the value if none was given by using the default setter.
#Component
public class Service {
#Value("${service.property:'This is my default setter string'}")
private String property;
}
Here my solution, just put that class in your code (just fix the "my.package" String):
/**
* Validates the environment-dependent properties during application start. Finds all spring beans, which classes are in
* defined package, validates them and in case of error tries to log the property name (not class field name), taken
* from {#link Value} annotation.
*
* #author Tomasz
*/
#Component
public class ConfigurationChecker implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger LOG = LoggerFactory.getLogger(ConfigurationChecker.class);
// this is a property, that is set in XML, so we bind it here to be found by checker. For properties wired directly in Beans using #Value just add validation constraints
#Value("${authorization.ldap.url}")
#NotBlank
private String ldapUrl;
private static final String FAIL_FAST_PROPERTY = "hibernate.validator.fail_fast";
private Validator validator = Validation.byDefaultProvider().configure().addProperty(FAIL_FAST_PROPERTY, "false")
.buildValidatorFactory().getValidator();
/**
* Performs the validation and writes all errors to the log.
*/
#SneakyThrows
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
LOG.info("Validating properties");
Set<ConstraintViolation<Object>> allViolations = new HashSet<>();
// Find all spring managed beans (including ConfigurationChecker)...
for (String beanName : event.getApplicationContext().getBeanDefinitionNames()) {
Object bean = event.getApplicationContext().getBean(beanName);
// ...but validate only ours.
if (bean.getClass().getCanonicalName().startsWith("my.package")) {
Set<ConstraintViolation<Object>> viol = this.validator.validate(bean);
LOG.info("Bean '" + beanName + "': " + (viol.isEmpty() ? " OK" : viol.size() + " errors found"));
allViolations.addAll(viol);
} else {
continue;
}
}
// if any error found...
if (allViolations.size() > 0) {
for (ConstraintViolation<Object> violation : allViolations) {
// ...extract "property.name" from field annotation like #Value("${property.name}")
String propertyName = violation.getLeafBean().getClass()
.getDeclaredField(violation.getPropertyPath().toString()).getAnnotation(Value.class).value();
propertyName = StringUtils.substring(propertyName, 2, -1);
// .. log it ..
LOG.error(propertyName + " " + violation.getMessage());
}
// ... and do not let the app start up.
throw new IllegalArgumentException("Invalid configuration detected. Please check the log for details.");
}
}
}
And here the test for it:
#RunWith(EasyMockRunner.class)
public class ConfigurationCheckerTest extends EasyMockSupport {
#TestSubject
private ConfigurationChecker checker = new ConfigurationChecker();
#Mock
private ContextRefreshedEvent event;
#Mock
private ApplicationContext applicationContext;
#Test(expected = IllegalArgumentException.class)
public void test() {
expect(this.event.getApplicationContext()).andReturn(this.applicationContext).anyTimes();
expect(this.applicationContext.getBeanDefinitionNames()).andReturn(new String[] { "configurationChecker" });
expect(this.applicationContext.getBean("configurationChecker")).andReturn(this.checker);
replayAll();
this.checker.onApplicationEvent(this.event);
verifyAll();
}
}