I was trying to follow multiple tutorials about the subject but I cannot understand how they support DI. To my basic understanding, DI should be supported by supplying extended/implemented classes object to the test, so the test will be able to be executed with multiple variations of the objects.
For example:
#Test
public void myTest(Base baseObj){
assertThis(baseObj);
assertThat(baseObj);
}
class Base {
//data
//methods
}
class Class1 extends Base{}
class Class2 extends Base{}
class Class3 extends Base{}
There should be a way to supply objects of the derived classes to the test. Am I wrong till here?
I couldn't understand from the explanations, how TestInfo class for example (or the other classes) helps me with that?
Can you enlighten me please?
You can do it like this:
public class Test1 {
#ParameterizedTest
#MethodSource("myTest_Arguments")
public void myTest(Base baseObj){
System.out.println(baseObj);
}
static Stream<Arguments> myTest_Arguments() {
return Stream.of(
Arguments.of(new Class1()),
Arguments.of(new Class2()),
Arguments.of(new Class3()));
}
}
The entire code is:
package com.example.demo;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
class Base {}
class Class1 extends Base{}
class Class2 extends Base{}
class Class3 extends Base{}
public class Test1 {
#ParameterizedTest
#MethodSource("myTest_Arguments")
public void myTest(Base baseObj){
System.out.println(baseObj);
}
static Stream<Arguments> myTest_Arguments() {
return Stream.of(Arguments.of(new Class1()),Arguments.of(new Class2()),Arguments.of(new Class3()));
}
}
and the dependencies used are:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.0</version>
<scope>test</scope>
</dependency>
I want to mock the static method being invoked from the constructor of my class.
My class:
package com.javaeasily.demos.junit;
import java.util.ArrayList;
public class MyClass {
private int number;
private static final ArrayList<String> ACTIVE_SERVICES_POST_RECONFIGURE = new ArrayList<>();
// Only allow construction if number is greater than one
MyClass() {
ACTIVE_SERVICES_POST_RECONFIGURE.add("my-node-" + NodeUtils.getMyNode());
}
public void reconfigureNode() {
if (ACTIVE_SERVICES_POST_RECONFIGURE.isEmpty()) {
return;
}
}
}
Here NodeUtils.getMyNode() is the static method being invoked from the constructor of the class.
NodeUtils.java Class:
package com.javaeasily.demos.junit;
import org.apache.maven.surefire.shade.booter.org.apache.commons.lang3.StringUtils;
public class NodeUtils {
private static final String HOSTNAME_PREFIX = "my-node-";
public static String hostnameToNode(String hostname) {
if (!hostname.startsWith(HOSTNAME_PREFIX)) {
throw new IllegalArgumentException(hostname + " is not recognized hostname");
}
return StringUtils.removeStart(hostname, HOSTNAME_PREFIX);
}
public static String getHostname() {
return System.getenv("HOSTNAME");
}
public static String getMyNode() {
return hostnameToNode(getHostname());
}
}
MyClassTest.java
package com.javaeasily.demos.junit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class MyClassTest {
private MyClass myclass;
#BeforeEach
public void SetUp() {
myclass = new MyClass();
}
#Test
public void testReconfigureNode() {
myclass.reconfigureNode();
}
}
When I try & run the only test case I get the following error:
java.lang.NullPointerException
at com.javaeasily.demos.junit.NodeUtils.hostnameToNode(NodeUtils.java:8)
at com.javaeasily.demos.junit.NodeUtils.getMyNode(NodeUtils.java:19)
at com.javaeasily.demos.junit.MyClass.<init>(MyClass.java:12)
at com.javaeasily.demos.junit.MyClassTest.SetUp(MyClassTest.java:11)
I am not sure how do we mock the method to avoid this error?
Since I am new to Java I am not able to catch this. Any help here is appreciated.
So to answer your question how to mock a static method: mockito allows this since version 3.8.0. You can find a tutorial here at Baeldung
This allows generating a statically mocked Object for a concrete context, which you can create within a try block. For your case this would look like the following.
Fixed Unit Test
package com.javaeasily.demos.junit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
#ExtendWith(MockitoExtension.class)
public class MyClassTest {
private MyClass myclass;
#BeforeEach
public void SetUp() {
try (MockedStatic<NodeUtils> nodeUtilsMockedStatic = Mockito.mockStatic(NodeUtils.class)) {
nodeUtilsMockedStatic.when(NodeUtils::getMyNode).thenReturn("foo");
myclass = new MyClass();
}
}
#Test
public void testReconfigureNode() {
myclass.reconfigureNode();
}
}
Mockito dependency
You need mockito with at least version 3.8.0 in your project.
With maven add:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
With gradle add:
testImplementation group: 'org.mockito', name: 'mockito-inline', version: '3.8.0'
I'm trying to spy my service class but I'm getting below exception, can you please help what I'm doing wrong here:
I tried to create Spy object using below code but is not working as expected
def myService = Spy(MyService)
MyInterface.groovy
interface MyInterface<T> {
public String welcome(T t);
}
MyService.groovy
#Service
class MyService implements MyInterface<WelcomeMessage> {
#Override
String welcome(WelcomeMessage welcomeMessage) {
try {
// Business logic
} catch (ex) {
// Catch Exception
}
}
}
import spock.lang.Specification
class myServiceTest extends Specification {
def "testWelcome"() {
setup: "create mock object"
def myService = Spy(MyService)
and: " and object with mock data"
when: "invoke welcomeMessage"
then: "Expecting no exception is thrown"
}
}
Exception:
java.lang.IllegalArgumentException
at net.sf.cglib.proxy.BridgeMethodResolver.resolveAll(BridgeMethodResolver.java:61)
at net.sf.cglib.proxy.Enhancer.emitMethods(Enhancer.java:911)
at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:498)
at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at net.sf.cglib.proxy.Enhancer.createClass(Enhancer.java:317)
at org.spockframework.mock.runtime.ProxyBasedMockFactory$CglibMockFactory.createMock(ProxyBasedMockFactory.java:154)
at org.spockframework.mock.runtime.ProxyBasedMockFactory.create(ProxyBasedMockFactory.java:68)
at org.spockframework.mock.runtime.JavaMockFactory.createInternal(JavaMockFactory.java:59)
at org.spockframework.mock.runtime.JavaMockFactory.create(JavaMockFactory.java:40)
at org.spockframework.mock.runtime.CompositeMockFactory.create(CompositeMockFactory.java:44)
at org.spockframework.lang.SpecInternals.createMock(SpecInternals.java:51)
at org.spockframework.lang.SpecInternals.createMockImpl(SpecInternals.java:296)
at org.spockframework.lang.SpecInternals.createMockImpl(SpecInternals.java:286)
at org.spockframework.lang.SpecInternals.SpyImpl(SpecInternals.java:169)
Thanks for your support
Could you provide your versions of spring, spock and cglib?
For these ones I could not reproduce the described issue:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.2-groovy-2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
<scope>test</scope>
</dependency>
The code I have is almost the same (instead WelcomeMessage I use String):
MyInterface.groovy
interface MyInterface<T> {
String welcome(T t);
}
MyService.groovy
import org.springframework.stereotype.Service
#Service
class MyService implements MyInterface<String> {
#Override
String welcome(String welcomeMessage) {
return welcomeMessage
}
}
MyServiceTest.groovy
import spock.lang.Specification
class MyServiceTest extends Specification {
def "Welcome"() {
setup: "create mock object"
def myService = Spy(MyService)
when: "invoke welcomeMessage"
def actual = myService.welcome("any")
then:
actual == "any"
}
}
Is it possible to somehow intercept the logging (SLF4J + logback) and get an InputStream (or something else that is readable) via a JUnit test case...?
The Slf4j API doesn't provide such a way but Logback provides a simple solution.
You can use ListAppender : a whitebox logback appender where log entries are added in a public List field that we could use to make our assertions.
Here is a simple example.
Foo class :
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Foo {
static final Logger LOGGER = LoggerFactory.getLogger(Foo .class);
public void doThat() {
logger.info("start");
//...
logger.info("finish");
}
}
FooTest class :
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
public class FooTest {
#Test
void doThat() throws Exception {
// get Logback Logger
Logger fooLogger = (Logger) LoggerFactory.getLogger(Foo.class);
// create and start a ListAppender
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
listAppender.start();
// add the appender to the logger
fooLogger.addAppender(listAppender);
// call method under test
Foo foo = new Foo();
foo.doThat();
// JUnit assertions
List<ILoggingEvent> logsList = listAppender.list;
assertEquals("start", logsList.get(0)
.getMessage());
assertEquals(Level.INFO, logsList.get(0)
.getLevel());
assertEquals("finish", logsList.get(1)
.getMessage());
assertEquals(Level.INFO, logsList.get(1)
.getLevel());
}
}
You can also use Matcher/assertion libraries as AssertJ or Hamcrest.
With AssertJ it would be :
import org.assertj.core.api.Assertions;
Assertions.assertThat(listAppender.list)
.extracting(ILoggingEvent::getFormattedMessage, ILoggingEvent::getLevel)
.containsExactly(Tuple.tuple("start", Level.INFO), Tuple.tuple("finish", Level.INFO));
You can create a custom appender
public class TestAppender extends AppenderBase<LoggingEvent> {
static List<LoggingEvent> events = new ArrayList<>();
#Override
protected void append(LoggingEvent e) {
events.add(e);
}
}
and configure logback-test.xml to use it. Now we can check logging events from our test:
#Test
public void test() {
...
Assert.assertEquals(1, TestAppender.events.size());
...
}
NOTE: Use ILoggingEvent if you do not get any output - see the comment section for the reasoning.
With JUnit5
private ListAppender<ILoggingEvent> logWatcher;
#BeforeEach
void setup() {
logWatcher = new ListAppender<>();
logWatcher.start();
((Logger) LoggerFactory.getLogger(MyClass.class)).addAppender(logWatcher);
}
Note: MyClass.class - should be your Prod class, you expect the log output from
use: (AssertJ example)
#Test
void myMethod_logs2Messages() {
...
int logSize = logWatcher.list.size();
assertThat(logWatcher.list.get(logSize - 2).getFormattedMessage()).contains("EXPECTED MSG 1");
assertThat(logWatcher.list.get(logSize - 1).getFormattedMessage()).contains("EXPECTED MSG 2");
}
destroy:
Detach is recommended, for a better performance:
#AfterEach
void teardown() {
((Logger) LoggerFactory.getLogger(MyClass.class)).detachAndStopAllAppenders();
}
imports:
import org.slf4j.LoggerFactory;
import ch.qos.logback.core.read.ListAppender;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.Logger;
credits to: #davidxxx's answer. See it for import ch.qos.logback... details: https://stackoverflow.com/a/52229629/601844
You can use slf4j-test from http://projects.lidalia.org.uk/slf4j-test/.
It replaces the entire logback slf4j implementation by it's own slf4j api implementation for tests and provides an api to assert against logging events.
example:
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<classpathDependencyExcludes>
<classpathDependencyExcludes>ch.qos.logback:logback-classic</classpathDependencyExcludes>
</classpathDependencyExcludes>
</configuration>
</plugin>
</plugins>
</build>
public class Slf4jUser {
private static final Logger logger = LoggerFactory.getLogger(Slf4jUser.class);
public void aMethodThatLogs() {
logger.info("Hello World!");
}
}
public class Slf4jUserTest {
Slf4jUser slf4jUser = new Slf4jUser();
TestLogger logger = TestLoggerFactory.getTestLogger(Slf4jUser.class);
#Test
public void aMethodThatLogsLogsAsExpected() {
slf4jUser.aMethodThatLogs();
assertThat(logger.getLoggingEvents(), is(asList(info("Hello World!"))));
}
#After
public void clearLoggers() {
TestLoggerFactory.clear();
}
}
A simple solution could be to mock the appender with Mockito (for example)
MyClass.java
#Slf4j
class MyClass {
public void doSomething() {
log.info("I'm on it!");
}
}
MyClassTest.java
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.verify;
#RunWith(MockitoJUnitRunner.class)
public class MyClassTest {
#Mock private Appender<ILoggingEvent> mockAppender;
private MyClass sut = new MyClass();
#Before
public void setUp() {
Logger logger = (Logger) LoggerFactory.getLogger(MyClass.class.getName());
logger.addAppender(mockAppender);
}
#Test
public void shouldLogInCaseOfError() {
sut.doSomething();
verify(mockAppender).doAppend(ArgumentMatchers.argThat(argument -> {
assertThat(argument.getMessage(), containsString("I'm on it!"));
assertThat(argument.getLevel(), is(Level.INFO));
return true;
}));
}
}
NOTE: I'm using assertion rather than returning false as it makes code and (possible) error easier to read, but it won't work if you have multiple verifications. In that case you need to return boolean indicating if the value is as expected.
Although creating a custom logback appender is a good solution, it is only the first step, you will eventually end up developing/reinventing slf4j-test, and if you go a bit further: spf4j-slf4j-test or other frameworks that I don't know of yet.
You will eventually need to worry about how many events you keep in memory, fail unit tests when a error is logged (and not asserted), make debug logs available on test failure, etc...
Disclaimer: I am the author of spf4j-slf4j-test, I wrote this backend to be able to better test spf4j, which is a good place to look at for examples on how to use spf4j-slf4j-test. One of the main advantages I achieved was reducing my build output (which is limited with Travis), while still having all the detail I need when failure happens.
I would recommend a simple, reusable spy implementation that can be included in a test as JUnit rule:
public final class LogSpy extends ExternalResource {
private Logger logger;
private ListAppender<ILoggingEvent> appender;
#Override
protected void before() {
appender = new ListAppender<>();
logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); // cast from facade (SLF4J) to implementation class (logback)
logger.addAppender(appender);
appender.start();
}
#Override
protected void after() {
logger.detachAppender(appender);
}
public List<ILoggingEvent> getEvents() {
if (appender == null) {
throw new UnexpectedTestError("LogSpy needs to be annotated with #Rule");
}
return appender.list;
}
}
In your test, you'd activate the spy in the following way:
#Rule
public LogSpy log = new LogSpy();
Call log.getEvents() (or other, custom methods) to check the logged events.
I had problems when testing logs line like: LOGGER.error(message, exception).
The solution described in http://projects.lidalia.org.uk/slf4j-test/ tries to assert as well on the exception and it is not easy (and in my opinion worthless) to recreate the stacktrace.
I resolved in this way:
import org.junit.Test;
import org.slf4j.Logger;
import uk.org.lidalia.slf4jext.LoggerFactory;
import uk.org.lidalia.slf4jtest.TestLogger;
import uk.org.lidalia.slf4jtest.TestLoggerFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;
import static uk.org.lidalia.slf4jext.Level.ERROR;
import static uk.org.lidalia.slf4jext.Level.INFO;
public class Slf4jLoggerTest {
private static final Logger LOGGER = LoggerFactory.getLogger(Slf4jLoggerTest.class);
private void methodUnderTestInSomeClassInProductionCode() {
LOGGER.info("info message");
LOGGER.error("error message");
LOGGER.error("error message with exception", new RuntimeException("this part is not tested"));
}
private static final TestLogger TEST_LOGGER = TestLoggerFactory.getTestLogger(Slf4jLoggerTest.class);
#Test
public void testForMethod() throws Exception {
// when
methodUnderTestInSomeClassInProductionCode();
// then
assertThat(TEST_LOGGER.getLoggingEvents()).extracting("level", "message").contains(
tuple(INFO, "info message"),
tuple(ERROR, "error message"),
tuple(ERROR, "error message with exception")
);
}
}
This has as well the advantage to not having depend on Hamcrest matchers library.
This is an alternative using lambdas that makes the log capturing logic reusable among tests (encapsulating its implementation) and doesn't require #BeforeEach/#AfterEach (in some of the proposed solutions the appender is not detached, which can lead to memory leaks).
Code under test:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyService {
private static final Logger LOG = LoggerFactory.getLogger(MyService.class);
public void doSomething(String someInput) {
...
LOG.info("processing request with input {}", someInput);
...
}
}
Interceptor helper:
package mypackage.util
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.slf4j.LoggerFactory;
import java.util.List;
public class LogInterceptor {
public static List<ILoggingEvent> interceptLogs(Class<?> klass, Runnable runnable) {
final Logger logger = (Logger) LoggerFactory.getLogger(klass);
final ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
listAppender.start();
logger.addAppender(listAppender);
try {
runnable.run();
return listAppender.list;
} finally {
logger.detachAppender(listAppender);
}
}
}
Test suite:
import static mypackage.util.LogInterceptor.interceptLogs;
public class MyServiceTest {
private MyService myService;
...
#Test
void doSomethingLogsLineWithTheGivenInput() {
List<ILoggingEvent> logs = interceptLogs(
myService.getClass(),
() -> myService.doSomething("foo")
);
assertThat(logs).isNotEmpty();
ILoggingEvent logEntry = logs.get(0);
assertThat(logEntry.getFormattedMessage()).isEqualTo("Processing request with input foo");
assertThat(logEntry.getLevel()).isEqualTo(Level.INFO);
}
}
I have a java class - very typical of the usual singleton - like this :
PLEASE NOTE : I have left out the "if null" logic here, for the sake of brevity, because that isn't what I am having trouble with, and I don't want to crowd the question.
public class MySingleton
{
ObjectMapper mapper;
private MySingleton()
{
new MySingleton(new ObjectMapper())
}
private MySingleton(ObjectMapper mapper)
{
this.mapper = mapper;
}
private static final class Lazy
{
static final MySingleton INSTANCE = new MySingleton();
}
public static MySingleton getInstance()
{
return Lazy.INSTANCE;
}
}
Now - that is great - and that works - but what if I am trying to test this in a unit test...
I want to mock the mapper - so I can do :
ObjectMapper mockObjectMapper = mock(ObjectMapper.class)
But, then when I need to somehow call the constructor of "MySingleton" in order to test it...
How do I do that - given that from my test class, I know it will say "MySingleton(arguments here) has private access in MySingleton"?
Singletons are the enemies of testable code. The links given in the answer to that question are an excellent argumentation on what's evil with singletons as well as why and how to avoid them.
You can use PowerMock to inject your test ObjectMapper instance using ConstructorMocking.
http://benkiefer.com/blog/2013/04/23/powermockito-constructor-mocking/
I had to modify your example singleton so that the constructors were chained correctly.
public class MySingleton {
ObjectMapper mapper;
private MySingleton()
{
//This does not work.
//new MySingleton(new ObjectMapper());
this(new ObjectMapper());
}
private MySingleton(ObjectMapper mapper)
{
this.mapper = mapper;
}
private static final class Lazy
{
static final MySingleton INSTANCE = new MySingleton();
}
public static MySingleton getInstance()
{
return Lazy.INSTANCE;
}
}
I also stubbed the ObjectMapper Class.
public class ObjectMapper {
//Empty Sample uses default CTR
}
I was able to test this as follows using the instructions from the link previously listed:
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
#RunWith(PowerMockRunner.class)
#PrepareForTest(MySingleton.class)
public class MySingletonTest {
#Test
public void testSingletonCtr() throws Exception {
ObjectMapper mapper = new ObjectMapper();
PowerMockito.whenNew(ObjectMapper.class).withNoArguments().thenReturn(mapper);
Assert.assertEquals(MySingleton.getInstance().mapper, mapper);
}
}
I am doing this in a maven project. I needed the following dependencies added to my test scope:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>1.6.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-rule</artifactId>
<version>1.6.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.5</version>
<scope>test</scope>
</dependency>
I do tend to agree that Singletons tend to cause problems long-term for code maintenance and scalability. If you have capacity to look for alternative approaches to your problem it may benefit you to do so. If not, then I believe that the PowerMock utility will provide you the capability you're looking for.
Best of Luck.