I am trying to write a unit test to a custom deserializer that is instantiated using a constructor with an #Autowired parameter and my entity marked with #JsonDeserialize. It works fine in my integration tests where a MockMvc brings up spring serverside.
However with tests where objectMapper.readValue(...) is being called, a new instance of deserializer using default constructor with no parameters is instantiated. Even though
#Bean
public MyDeserializer deserializer(ExternalObject externalObject)
instantiates wired version of deserializer, real call is still passed to empty constructor and context is not being filled up.
I tried manually instantiating of a deserializer instance and registering it in ObjectMapper, but it only works if I remove #JsonDeserialize from my entity class (and it breaks my integration tests even if I do the same in my #Configuration class.) - looks related to this: https://github.com/FasterXML/jackson-databind/issues/1300
I can still test the deserializer behavior calling deserializer.deserialize(...) directly, but this approach doesn't work for me in tests that are not Deserializer's unit tests...
UPD: working code below
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.github.tomakehurst.wiremock.common.Json;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import java.io.IOException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
#JsonTest
#RunWith(SpringRunner.class)
public class JacksonInjectExample {
private static final String JSON = "{\"field1\":\"value1\", \"field2\":123}";
public static class ExternalObject {
#Override
public String toString() {
return "MyExternalObject";
}
}
#JsonDeserialize(using = MyDeserializer.class)
public static class MyEntity {
public String field1;
public String field2;
public String name;
public MyEntity(ExternalObject eo) {
name = eo.toString();
}
#Override
public String toString() {
return name;
}
}
#Component
public static class MyDeserializer extends JsonDeserializer<MyEntity> {
#Autowired
private ExternalObject external;
public MyDeserializer() {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
public MyDeserializer(#JacksonInject final ExternalObject external) {
this.external = external;
}
#Override
public MyEntity deserialize(JsonParser p, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
return new MyEntity(external);
}
}
#Configuration
public static class TestConfiguration {
#Bean
public ExternalObject externalObject() {
return new ExternalObject();
}
#Bean
public MyDeserializer deserializer(ExternalObject externalObject) {
return new MyDeserializer(externalObject);
}
}
#Test
public void main() throws IOException {
HandlerInstantiator hi = mock(HandlerInstantiator.class);
MyDeserializer deserializer = new MyDeserializer();
deserializer.external = new ExternalObject();
doReturn(deserializer).when(hi).deserializerInstance(any(), any(), eq(MyDeserializer.class));
final ObjectMapper mapper = Json.getObjectMapper();
mapper.setHandlerInstantiator(hi);
final MyEntity entity = mapper.readValue(JSON, MyEntity.class);
Assert.assertEquals("MyExternalObject", entity.name);
}
}
I don't know how to set this particularly using Jackson injection, but you can test it using spring Json tests. I think this method is closer to the real scenario and much more simplier. Spring will load only related to serialization/deserialization beans, thus you have to provide only custom beans or mocks instead them.
#JsonTest
public class JacksonInjectExample {
private static final String JSON = "{\"field1\":\"value1\", \"field2\":123}";
#Autowired
private JacksonTester<MyEntity> jacksonTester;
#Configuration
public static class TestConfiguration {
#Bean
public ExternalObject externalObject() {
return new ExternalObject();
}
}
#Test
public void test() throws IOException {
MyEntity result = jacksonTester.parseObject(JSON);
assertThat(result.getName()).isEqualTo("MyExternalObject");
}
If you would like to use mocks use following snippet:
#MockBean
private ExternalObject externalObject;
#Test
public void test() throws IOException {
when(externalObject.toString()).thenReturn("Any string");
MyEntity result = jacksonTester.parseObject(JSON);
assertThat(result.getName()).isEqualTo("Any string");
}
Very interesting question, it made me wonder how autowiring into jackson deserializers actually works in a spring application. The jackson facility that is used seems to be the HandlerInstantiator interface, which is configured by spring to the SpringHandlerInstantiator implementation, which just looks up the class in the application context.
So in theory you could setup an ObjectMapper in your unit test with your own (mocked) HandlerInstantiator, returning a prepared instance from deserializerInstance(). It seems to be fine to return null for other methods or when the class parameter does not match, this will cause jackson to create the instance on its own.
However, I do not think this is a good way to unit test deserialization logic, as the ObjectMapper setup is necessarily different from what is used during actual application execution. Using the JsonTest annotation as suggested in Anton's answer would be a much better approach, as you are getting the same json configuration that would be used during runtime.
Unit tests should not depend upon or invoke other major classes or frameworks. This is especially true if there are also integration or acceptance tests covering the functioning of the application with a particular set of dependencies as you describe. So it would be best to write the unit test so that it has a single class as its subject i.e. calling deserializer.deserialize(...) directly.
In this case a unit test should consist of instanciating a MyDeserializer with a mocked or stubbed ExternalObject, then testing that its deserialize() method returns a MyEntity correctly for different states of the JsonParser and DeserializationContext arguments. Mockito is really good for setting up mock dependencies!
By using an ObjectMapper in the unit test, quite a lot of code from the Jackson framework is also being invoked in each run - so the test is not verifying the contract of MyDeserializer, it is verifying the behaviour of the combination of MyDeserializer and a particular release of Jackson. If there is a failure of the test it won't be immediatly clear which of all the components involved is at fault. And because setting up the environment of the two frameworks together is more difficult the test will prove brittle over time and fail more often due to issues with the setup in the test class.
The Jackson framework is responsible for writing unit tests of ObjectMapper.readValue and constructors using #JacksonInject. For the 'other unit tests that are not Deserializer's unit tests' - it would be best to mock/stub the MyDeserializer (or other dependencies) for that test. That way the other class's logic is being isolated from the logic in MyDeserializer - and the other class's contracts can be verified without being qualified by the behaviour of code outside of the unit under test.
Related
I have an abstract class (database mapping) implementing an interface where default implementations are injected at runtime (this is part of another library and cannot be changed).
I want to override one of the default implementation via a proxy (as that seems like the way to override this).
public abstract class Table1 implements Storable<Table1>
{
#Sequence("ID_SEQUENCE")
#Alias("ID")
public abstract String getID();
public abstract void setID(String ID);
#Alias("NAME")
public abstract String getAvailabilityZone();
public abstract void setAvailabilityZone(String value);
}
public interface Storable<S extends Storable<S>> {
//a bunch of method definition.
boolean tryLoad() throws Exception;
}
Let's say I want to override tryLoad() method to do my own things instead of what the generated code provides. Given the nature of the library, it is not something I can achieve by simple #Override.
The simple way this is currently used is as following:
public void method() {
Table1 t = Repository.storageFor(Table1.class).prepare();
t.setName( "temp" );
if (!t.tryLoad())
t.tryInsert();
}
I want to proxy tryLoad() without making changes in all the methods across the whole codebase - that would be to get proxied instance instead of actual one and perform the operation on that.
Is there any recommended way to achieve this?
Thanks!
I woke up last night and felt bored, so despite your lack of feedback I created a little Carbonado showcase project and shared it on GitHub. I made three commits:
Initial commit with Maven project already prepared for AspectJ and a JUnit test for me to find out how Carbonado actually works, because I had never used it before.
Add failing unit test for behaviour of tryLoad() expected to be provided by aspect.
Add aspect to make unit test pass. Aspect hooks into tryLoad() and auto-creates non-existent record. I do not know if I guessed right what you actually wanted to achieve, but if it was a different thing, just change the aspect implementation.
Sample code
Carbonado storable:
package de.scrum_master.app;
import com.amazon.carbonado.Nullable;
import com.amazon.carbonado.PrimaryKey;
import com.amazon.carbonado.Storable;
#PrimaryKey("ID")
public interface StoredMessage extends Storable<StoredMessage> {
long getID();
void setID(long id);
#Nullable String getMessage();
void setMessage(String message);
}
Aspect:
package de.scrum_master.aspect;
import com.amazon.carbonado.Storable;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class CarbonadoAspect {
#Around("call(boolean tryLoad()) && target(storable)")
public boolean tryInsertIfNotFound(ProceedingJoinPoint thisJoinPoint, Storable storable) throws Throwable {
System.out.println(thisJoinPoint);
if ((boolean) thisJoinPoint.proceed())
return true;
System.out.println("Not found: " + storable + " -> inserting");
return storable.tryInsert();
}
}
JUnit test:
package de.scrum_master.app;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.PersistException;
import com.amazon.carbonado.Repository;
import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storage;
import com.amazon.carbonado.SupportException;
import com.amazon.carbonado.repo.map.MapRepositoryBuilder;
import de.scrum_master.app.StoredMessage;
public class CarbonadoTest {
private Repository repo;
private Storage<StoredMessage> storage;
StoredMessage message;
#Before
public void setUp() throws Exception {
repo = MapRepositoryBuilder.newRepository();
storage = repo.storageFor(StoredMessage.class);
message = storage.prepare();
}
#After
public void tearDown() throws Exception {
repo.close();
repo = null;
storage = null;
message = null;
}
// (...)
#Test
public void aspectCreatesNonExistentRecord() throws SupportException, RepositoryException {
message.setID(1);
// Without the aspect this would be false
assertTrue(message.tryLoad());
assertEquals(message.getID(), 1);
assertEquals(message.getMessage(), null);
}
}
Enjoy!
I'm pondering this builder class that should calculate a hash from the field values. Maybe this in itself is wrong for starters, but at the moment it seems to me that it belongs there because I'm striving to an immutable Article.
I would like to autowire/inject ArticleMD5HashCalculator but when I put #Autowired on the field, IntelliJ complains: field injection is not recommended. Constructor injection is not possible because it's a builder pattern class, which means it has a private constructor without parameters and a static method for instantiation where it would be awkward to pass in hashCalculator.
The builder is injected into a scraper. The scraper will reuse the same builder for many articles. When Spring creates the builder with prototype scope, the builder will carry old values when the next article doesn't overwrite the old values.
New'ing the hashCalculator results is a hard dependency, making it impractical to inject mocks. What is the best way to handle this situation?
Here's the code of how it is now:
import org.observer.media.utils.ArticleMD5HashCalculator;
import org.observer.media.utils.MD5HashCalculator;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ArticleBuilder {
private ArticleMD5HashCalculator hashCalculator;
private String headline;
private String subheading;
private String lead;
// other article fields...
private ArticleBuilder() {
// This seems wrong.
this.hashCalculator = new ArticleMD5HashCalculator(new MD5HashCalculator());
}
public static ArticleBuilder article() {
return new ArticleBuilder();
}
public ArticleBuilder withHeadline(String headline) {
this.headline = headline;
return this;
}
//Other with-methods...
public Article build() {
// calculateHash() is called in the 9th argument.
return new Article(headline, subheading, lead, body, images, quotations, subArticles, url, calculateHash(), author, sources, category, subjects, index, medium, company, datePublished, dateFetched);
}
private String calculateHash() {
return hashCalculator.hash(headline, subheading, lead, body, quotations, datePublished, dateFetched);
}
}
Assumptions:
There is one to one relationship between ArticleBuilder and ArticleMD5HashCalculator. Meaning you don't plan to inject different instances of hashCalculator into ArticleBuilder at different places in the project (essentially having multiple instances of ArticleBuilder)
You can change the ArticleBuilder impl as follows
public class ArticleBuilder {
private ArticleMD5HashCalculator hashCalculator;
public ArticleBuilder(ArticleMD5HashCalculator hashCalculator) {
this.hashCalculator = hashCalculator;
}
}
You can create a spring bean of type ArticleMD5HashCalculator and have this injected into a spring bean of type ArticleBuilder the following way.
#Configuration
public class ArticleConfig {
#Bean
public ArticleMD5HashCalculator articleMD5HashCalculator() {
return new ArticleMD5HashCalculator(new MD5HashCalculator());
}
#Bean
public ArticleBuilder() {
return new ArticleBuilder(articleMD5HashCalculator());
}
}
You can autowire ArticleBuilder elsewhere in your project and use it as a builder.
I am not sure why you made a private constructor and a static method to invoke that. I assume it is because you want a singleton ArticleBuilder. That can be achieved with the above approach. Correct me if I am wrong about this.
Update 1:
Based on the information you provided in the comments, you are injecting ArticleBuilder in a Scraper object and you want to have a way of getting a new instance of ArticleBuilder every time. You can use spring #Lookup annotation for that.
Stub implementation of Scraper class.
public class Scraper {
//assuming this is the method where you want to use ArticleBuilder
public void scrape() {
getArticleBuilder();
}
//You can even pass constructor arguments to this method.
//They will be used to match a constructor on the target bean and that gets invoked
#Lookup
public ArticleBuilder getArticleBuilder() {
//Spring creates a runtime implementation of this method.
return null;
}
}
You can call getArticleBuilder anytime you want a new instance of the bean. If it is declared prototype, you will always get a new instance of the bean.
But the only caveat with this is that Lookup annotation is not going to work with beans created with #Bean annotation. You alternate config may look like this.
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ArticleBuilder {
#Autowired
private ArticleMD5HashCalculator hashCalculator;
public ArticleBuilder(ArticleMD5HashCalculator hashCalculator) {
this.hashCalculator = hashCalculator;
}
}
#Component
public class ArticleMD5HashCalculator {
public ArticleMD5HashCalculator(MD5HashCalculator hashCalculator) {
this.hashCalculator = hashCalculator;
}
}
beans.xml:
<beans>
<bean class="MD5HashCalculator" />
<!-- Fully qualified class name is needed -->
</beans>
Also due to convention used in Spring documentation please use constructor-based injection when possible.
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null.
Full info (scroll a little): Spring DOCS
I brewed up this alternative approach to pull out the new'ing and to open a window for injecting mocks. The solution implies that the builder has to be instantiated and recreated by a factory.
The factory:
import org.observer.media.hash.ArticleHashCalculator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
#Component
public class ArticleBuilderFactory {
private ArticleHashCalculator articleHashCalculator;
#Autowired
public ArticleBuilderFactory(ArticleHashCalculator articleHashCalculator) {
this.articleHashCalculator = articleHashCalculator;
}
public ArticleBuilder create() {
return new ArticleBuilder(articleHashCalculator);
}
public class ArticleBuilder {
private ArticleHashCalculator articleHashCalculator;
private String headline;
private String subheading;
//...
private ArticleBuilder(ArticleHashCalculator articleHashCalculator) {
this.articleHashCalculator = articleHashCalculator;
}
public ArticleBuilderFactory.ArticleBuilder withIndex(int index) {
this.index = index;
return this;
}
public ArticleBuilderFactory.ArticleBuilder withHeadline(String headline) {
this.headline = headline;
return this;
}
//...
public Article build() {
return new Article(headline, subheading, lead, body, images, quotations, subArticles, url, calculateHash(), author, sources, category, subjects, index, medium, company, datePublished, dateFetched);
}
private String calculateHash() {
return articleHashCalculator.hash(headline, subheading, lead, body, quotations, datePublished, dateFetched);
}
}
}
Usage of the factory:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.observer.media.hash.ArticleMD5HashCalculator;
import org.observer.media.hash.MD5HashCalculator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Java6Assertions.assertThat;
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {
ArticleBuilderFactory.class,
MD5HashCalculator.class,
ArticleMD5HashCalculator.class
})
public class ArticleBuilderFactoryTest {
private static final String HEADLINE = "headline";
private static final String LEAD = "lead";
private static final String BODY = "body";
#Autowired
private ArticleBuilderFactory articleBuilderFactory;
#Autowired
private ArticleMD5HashCalculator hashCalculator;
#Test
public void build() {
ArticleBuilderFactory.ArticleBuilder articleBuilder = articleBuilderFactory.create();
Article article = articleBuilder
.withHeadline(HEADLINE)
.withLead(LEAD)
.withBody(BODY)
.build();
assertThat(article.getHeadline()).isEqualTo(HEADLINE);
assertThat(article.getLead()).isEqualTo(LEAD);
assertThat(article.getBody()).isEqualTo(BODY);
assertThat(article.getHash()).isEqualTo(hashCalculator.hash(HEADLINE, null, LEAD, BODY, null, null, null));
}
}
ArticleMD5HashCalculator has #Component:
#Component
public class ArticleMD5HashCalculator {
}
Situation and Problem: In Spring Boot, how can I inject one or more mocked classes/beans into the application to do an integration test? There are a few answers on StackOverflow, but they are focused on the situation before Spring Boot 1.4 or are just not working for me.
The background is, that in the code bellow the implementation of Settings relies on third party servers and other external systems. The functionality of Settings is already tested in a unit test, so for a full integration test I want to mock out the dependency to these servers or system and just provide dummy values.
MockBean will ignore all existing bean definitions and provide a dummy object, but this object doesn't provide a method behavior in other classes that inject this class. Using the #Before way to set the behavior before a test doesn't influence the injected object or isn't injected in other application services like AuthenticationService.
My question: How can I inject my beans into the application context?
My test:
package ch.swaechter.testapp;
import ch.swaechter.testapp.utils.settings.Settings;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.junit4.SpringRunner;
#TestConfiguration
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MyApplication.class})
public class MyApplicationTests {
#MockBean
private Settings settings;
#Before
public void before() {
Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");
}
#Test
public void contextLoads() {
String applicationsecret = settings.getApplicationSecret();
System.out.println("Application secret: " + applicationsecret);
}
}
And bellow a service that should use the mocked class, but doesn't receive this mocked class:
package ch.swaechter.testapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class AuthenticationServiceImpl implements AuthenticationService {
private final Settings settings;
#Autowired
public AuthenticationServiceImpl(Settings settings) {
this.settings = settings;
}
#Override
public boolean loginUser(String token) {
// Use the application secret to check the token signature
// But here settings.getApplicationSecret() will return null (Instead of Application Secret as specified in the mock)!
return false;
}
}
Looks like you are using Settings object before you specify its mocked behavior.
You have to run
Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");
during configuration setup. For preventing that you can create special configuration class for test only.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MyApplication.class, MyApplicationTest.TestConfig.class})
public class MyApplicationTest {
private static final String SECRET = "Application Secret";
#TestConfiguration
public static class TestConfig {
#Bean
#Primary
public Settings settingsBean(){
Settings settings = Mockito.mock(Settings.class);
Mockito.when(settings.getApplicationSecret()).thenReturn(SECRET);
Mockito.doReturn(SECRET).when(settings).getApplicationSecret();
return settings;
}
}
.....
}
Also I would recommend you to use next notation for mocking:
Mockito.doReturn(SECRET).when(settings).getApplicationSecret();
It will not run settings::getApplicationSecret
When you annotate a field with #MockBean, spring will create a mock of the annotated class and use it to autowire all beans of the application context.
You must not create the mock yourself with
Settings settings = Mockito.mock(Settings.class);
this would create a second mock, leading to the described problem.
Solution :
#MockBean
private Settings settings;
#Before
public void before() {
Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");
}
#Test
public void contextLoads() {
String applicationsecret = settings.getApplicationSecret();
System.out.println("Application secret: " + applicationsecret);
}
Is it true that mockito can't mock objects that were already enhanced by CGLIB?
public class Article {
#Autowired
private dbRequestHandler
#Autowired
private filesystemRequestHandler
#Transactional
public ArticleDTO getArticleContents() {
//extractText() and then save the data in DTO
//extractImages() and then save the data in DTO
// some other calls to other databases to save data in dto
return articleDTO;
}
public void extractText() {
//call to DB
}
public void extractImages() {
// call to file system
}
}
public class IntegrationTest {
#Autowired
private Article article;
//setup method {
articleMock = Mockito.spy(article);
doNothing().when(articleMock).extractImages();
}
}
In the above example when it comes to doNothing().when(articleMock).extractImages(); it actually calls the real function. On a closer look articleMock gets enhanced two times. One cause of autowiring and second time cause of spying.
If I can't spy on enhaced objects, then how can I test the getArticle() method in my Integration test, so that I can verify a proper DTO is returned.
Note : I actually don't want to test the method which does filesystem calls. just the DB ones. thats why I need to test the getArticle method.
If I understand correctly your class is wired by Spring. Spring uses CGLIB to ensure transactional behaviour only if there is no interface, which is implemented by your object. If there is an interface, it uses simple JDK Dynamic Proxies. (see http://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.html)
Maybe you could try to extract an interface, and let Spring to use dynamic proxies. Maybe then Mockito could perform better.
If you run as a true unit test and not as an integration test, you need not run in a container having Spring autowire for you. In one of your comments, I think you alluded to trying this, and you noted that there was an endless set of chained object references which you would have to provide as well. But there is a way around that. Mockito provides some predefined Answer classes that you can initialize your mock with. You may want to look at RETURNS_DEEP_STUBS, which will possibly get you around this problem.
Will you please update your question with ready-to-go compilable code. Here's some code review suggestions:
Issues with this question code:
Article.java missing import: org.springframework.beans.factory.annotation.Autowired
Article.java missing import: org.springframework.transaction.annotation.Transactional
Article.java attribute syntax issue: dbRequestHandler
Article.java attribute syntax issue: filesystemRequestHandler
Article.java method has no initialized return statement: articleDTO
Here's what you maybe should use as you questionCode with the above issues fixed:
Article.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
public class Article {
#Autowired
private Object dbRequestHandler;
#Autowired
private Object filesystemRequestHandler;
#Transactional
public ArticleDTO getArticleContents() {
// extractText() and then save the data in DTO
// extractImages() and then save the data in DTO
// some other calls to other databases to save data in dto
ArticleDTO articleDTO = null;
return articleDTO;
}
public void extractText() {
// call to DB
}
public void extractImages() {
// call to file system
}
}
IntegrationTest.java is a poor name for a testClass because it's to generic. I would suggest ArticleTest for a java unit test.
ArticleTest.java
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.beans.factory.annotation.Autowired;
#RunWith(PowerMockRunner.class)
#PrepareForTest(ClassWithPrivate.class)
public class ArticleTest {
#InjectMocks
private Article cut;
#Mock
private Object dbRequestHandler;
#Mock
private Object filesystemRequestHandler;
#Test
public void testeExtractImages() {
/* Initialization */
Article articleMock = Mockito.spy(cut);
/* Mock Setup */
Mockito.doNothing().when(articleMock).extractImages();
/* Test Method */
ArticleDTO result = cut.getArticleContents();
/* Asserts */
Assert.assertNull(result);
}
}
You can utilize AdditionalAnswers.delegatesTo method. In following example, the secondProxyDoingMocking declaration creates something like a spy (compare with implementation of spy() method) except it uses "lightweight" method delegation.
import org.mockito.AdditionalAnswers;
public class ArticleTest {
#Autowired
private Article firstProxyDoingAutowiring;
#Test
public void testExtractImages() {
Article secondProxyDoingMocking = Mockito.mock(Article.class,
Mockito.withSettings().defaultAnswer(
AdditionalAnswers.delegatesTo(firstProxyDoingAutowiring)
)
);
Mockito.doNothing().when(secondProxyDoingMocking).extractImages();
...
}
}
I didn't test this example, however I assembled it from my working code. My use case was similar: return constant value for given method, call real method for all remaining methods of Spring #Transactional-annotated bean.
Suppose I have a class like so:
public class StaticDude{
public static Object getGroove() {
// ... some complex logic which returns an object
};
}
How do I mock the static method call using easy mock? StaticDude.getGroove().
I am using easy mock 3.0
Not sure how to with pure EasyMock, but consider using the PowerMock extensions to EasyMock.
It has a lot of cool functions for doing just what you need -
https://github.com/jayway/powermock/wiki/MockStatic
Easymock is a testing framework for "for interfaces (and objects through the class extension)" so you can mock a class without an interface. Consider creating an interfaced object with an accessor for your static class and then mock that acessor instead.
EDIT: Btw, I wouldn't recommend doing static classes. It is better to have everything interfaced if you are doing TDD.
Just in Case PowerMock is unavailable for any reason:
You could move the static call to a method, override this method in the instantiation of the tested class in the test class, create a local interface in the test class and use its method in the overidden method:
private interface IMocker
{
boolean doSomething();
}
IMocker imocker = EasyMock.createMock(IMocker.class);
...
#Override
void doSomething()
{
imocker.doSomething();
}
...
EasyMock.expect(imocker.doSomething()).andReturn(true);
Generally speaking, it is not possible to mock a static method without using some sort of accessor, which seems to defeat the purpose of using a static method. It can be quite frustrating.
There is one tool that I know of called "TypeMock Isolator" which uses some sort of Satanic Magic to mock static methods, but that tool is quite expensive.
The problem is, I know of no way to override a static method. You can't declare it virtual. you can't include it in an interface.
Sorry to be a negative nelly.
Adding an exemple on how to implements static mock along regular mock of injected classes with EasyMock / PowerMock, since the linked exemple only shows static mock.
And with the PowerMockRunner the #Mock services are not wired on the #TestSubject service to test.
Let say we have a service we want to test, ServiceOne :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class ServiceOne {
#Autowired
private ServiceTwo serviceTwo;
public String methodToTest() {
String returnServ2 = serviceTwo.methodToMock();
return ServiceUtils.addPlus(returnServ2);
}
}
Which calls another service that we will want to mock, ServiceTwo :
import org.springframework.stereotype.Service;
#Service
public class ServiceTwo {
public String methodToMock() {
return "ServiceTwoReturn";
}
}
And which calls a final class static method, ServiceUtils :
public final class ServiceUtils {
public static String addPlus(String pParam) {
return "+" + pParam;
}
}
When calling ServiceOne.methodToTest() we get "+ServiceTwoReturn" as a return.
Junit Test with EasyMock, mocking only the injected ServiceTwo Spring service :
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import org.easymock.EasyMockRunner;
import org.easymock.Mock;
import org.easymock.TestSubject;
import org.junit.Test;
import org.junit.runner.RunWith;
#RunWith(EasyMockRunner.class)
public class ExempleTest {
#TestSubject
private ServiceOne serviceToTest = new ServiceOne();
#Mock
private ServiceTwo serviceMocked;
#Test
public void testMethodToTest() {
String mockedReturn = "return2";
expect(serviceMocked.methodToMock()).andReturn(mockedReturn);
replay(serviceMocked);
String result = serviceToTest.methodToTest();
verify(serviceMocked);
assertEquals("+" + mockedReturn, result);
}
}
Junit Test with EasyMock & PowerMock, mocking the injected ServiceTwo Spring service but also the final class and its Static method :
import static org.easymock.EasyMock.expect;
import static org.junit.Assert.assertEquals;
import static org.powermock.api.easymock.PowerMock.createMock;
import static org.powermock.api.easymock.PowerMock.mockStatic;
import static org.powermock.reflect.Whitebox.setInternalState;
import org.easymock.Mock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
#RunWith(PowerMockRunner.class)
#PrepareForTest(ServiceUtils.class)
public class ExempleTest {
private ServiceOne serviceToTest;
private ServiceTwo serviceMocked;
#Before
public void setUp() {
serviceToTest = new ServiceOne();
serviceMocked = createMock(ServiceTwo.class);
// This will wire the serviced mocked into the service to test
setInternalState(serviceToTest, serviceMocked);
mockStatic(ServiceUtils.class);
}
#Test
public void testMethodToTest() {
String mockedReturn = "return2";
String mockedStaticReturn = "returnStatic";
expect(serviceMocked.methodToMock()).andReturn(mockedReturn);
expect(ServiceUtils.addPlus(mockedReturn)).andReturn(mockedStaticReturn);
PowerMock.replayAll();
String result = serviceToTest.methodToTest();
PowerMock.verifyAll();
assertEquals(mockedStaticReturn, result);
}
}