Should I use an autowired static variable? - java

I am trying to implement a singleton pattern with a caching feature. At first MySingleton was only a POJO and things were simple enough, but then I needed to add a new feature, which also required autowiring a bean. (MyComponent is really an interface to a data repository)
I put the #Component annotation on MySingleton to trigger the autowiring (even though it is always called a static way) and created a private constructor to pass the MyComponent reference to an object created by new. This code seems to work, although I do not fully understand why.
My question: I feel like I'm doing it wrong, but am I?
(would you approve this pull request to your code base?)
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
#SpringBootTest
public class MySingletonTest {
#Test
public void test() {
assertNotNull(MySingleton.getInstance().getMyComponent());
}
}
// ----------------------------------------------------------------------------- //
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class MySingleton {
private static final long CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour
private static final AtomicReference<MySingleton> INSTANCE = new AtomicReference<MySingleton>();
private final Calendar timestamp; // NOTE: this is NOT static!
#Autowired
private static MyComponent myComponent;
private MySingleton(MyComponent myComponent) {
this.timestamp = Calendar.getInstance();
MySingleton.myComponent = myComponent; // I do not understand why this line is needed
}
private boolean isTimeout() {
return Calendar.getInstance().getTimeInMillis() - timestamp.getTimeInMillis() > CACHE_TIMEOUT;
}
public static synchronized MySingleton getInstance() {
if ( INSTANCE.get() == null || INSTANCE.get().isTimeout() ) {
INSTANCE.set(new MySingleton(myComponent));
}
return INSTANCE.get();
}
public MyComponent getMyComponent() {
return myComponent;
}
}
// ----------------------------------------------------------------------------- //
import org.springframework.stereotype.Component;
#Component
public class MyComponent {
}

Related

How to use generic class with Mockito.when

I am trying to use a parameterized test and I want to use Mockito.when() the following scenario. I am not in a position where I can modify any code except for the test. I have put a comment on the line that is causing a compile error at the moment. I am having a hard time putting it into words, but basically I want to be able to mock the method without having access to the exact type during compile time.
Test class:
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Map;
import static com.example.demo.Type.A;
import static com.example.demo.Type.B;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.*;
#ExtendWith(MockitoExtension.class)
public class MyTest {
private static final Map<Type, Class<? extends MyStuff>> classByType = Map.of(A, StuffA.class, B, StuffB.class);
#InjectMocks
private Handler handler;
#Mock
private Converter converter;
#Mock
private Sender sender;
#Captor
private ArgumentCaptor<Thing> thingArgumentCaptor;
#ParameterizedTest
#EnumSource(value = Type.class)
void testHandle(Type type) {
MyStuff myStuff = mock(classByType.get(type));
Thing thing = mock(Thing.class);
when(myStuff.getType()).thenReturn(type);
when(converter.convert(classByType.get(type).cast(type))).thenReturn(thing); // This line is causing the compile error
handler.handle(myStuff);
verify(sender).send(thingArgumentCaptor.capture());
assertThat(thingArgumentCaptor.getValue()).isEqualTo(thing);
}
}
Class under test:
public class Handler {
private final Converter converter;
private final Sender sender;
public Handler(Converter converter, Sender sender) {
this.converter = converter;
this.sender = sender;
}
public void handle(MyStuff myStuff) {
Thing thing;
switch (myStuff.getType()) {
case A:
thing = converter.convert((StuffA) myStuff);
break;
case B:
thing = converter.convert((StuffB) myStuff);
break;
default:
throw new RuntimeException();
}
sender.send(thing);
}
}
Domain object:
public abstract class MyStuff {
public abstract Type getType();
}
Converter:
public interface Converter {
Thing convert(StuffA myType);
Thing convert(StuffB myType);
}
Sender:
public interface Sender {
void send(Thing thing);
}
Type:
public enum Type {
A, B
}
Is there any way to solve this without writing two separate test methods?
You ​can use reflection to call the appropriate method. Try replacing the line with the compilation error with the following two lines:
​Method method = Converter.class.getMethod("convert", classByType.get(type));
​when(method.invoke(converter, eq(myStuff))).thenReturn(thing);
If the type of the method parameter isn't known at compile time, then reflection is the only way to go.

How Spring's Cacheable Annotation can work for class initailized through new Keyword. (In a Class Constructor, initialized through Bean)

In our service, we are initializing a bean (say "A") and that internally constructing a CacheableService Object by using - new CacheableService(). And as I know spring's #Cacheable annotations won't work on class method if the class is initialized using "new" Keyword.
Then what is an alternative or a way to cache method response?
Scenario :
<bean class="com.package.src.A"/>
public class A {
Map<String, CacheableService> map;
public CacheableService2() {
map = new HashedMap();
map.put("a", new CacheableService());
}
}
import org.springframework.cache.annotation.Cacheable;
public class CacheableService {
#Cacheable(value = "entityCount", key = "#criteria.toString()")
public int someEntityCount(final String criteria) {
System.out.println("Inside function : " + criteria);
return 5;
}
}
Here is a minimum example which demonstrates caching using Spring Boot. The code for the examples below can be found here.
Go to https://start.spring.io/ and create a new Spring Boot project. Make sure to include "Spring cache abstraction" which results in this entry being added to your pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
Add the #EnableCaching annotation to your application:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
#EnableCaching
#SpringBootApplication
public class CacheableApplication {
public static void main(String[] args) {
SpringApplication.run(CacheableApplication.class, args);
}
}
Your service:
package com.example;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
#Service
public class CacheableService {
#Cacheable(value = "entityCount")
public int someEntityCount(final String criteria) {
System.out.print(String.format("Inside function: %s", criteria));
return 5;
}
}
Class A:
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class A {
private CacheableService cacheableService;
public A(#Autowired CacheableService cacheableService) {
this.cacheableService = cacheableService;
}
public int getEntityCount(String criteria) {
return cacheableService.someEntityCount(criteria);
}
}
And then here is a test that demonstrates that the caching is working. As you can see in the test a.getEntityCount("foo") is being called twice, but in standard out we only see "Inside function: foo" being printed once. Therefore we have verified that the second call resulted in the cache being used to produce the result.
package com.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest
class CacheableTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
#Autowired
private A a;
#BeforeEach
public void init() {
System.setOut(new PrintStream(outContent));
}
#Test
public void testCaching() {
a.getEntityCount("foo");
a.getEntityCount("foo");
assertEquals("Inside function: foo", outContent.toString());
}
}
EDIT:
If you want to move the cache outside of the Spring lifecycle and manually manage it then I would recommend using Caffeine. Here is the same example but now without any Spring involved.
Your service:
package com.example.withoutspring;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.util.concurrent.TimeUnit;
public class CaffeineCachingService {
private LoadingCache<String, Integer> entityCountCache = Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES)
.build(key -> someEntityCount(key));
public int cachedEntityCount(final String criteria) {
return entityCountCache.get(criteria);
}
private int someEntityCount(final String criteria) {
System.out.print(String.format("Inside function: %s", criteria));
return 5;
}
}
Class B:
package com.example.withoutspring;
public class B {
private CaffeineCachingService cacheableService;
public B() {
cacheableService = new CaffeineCachingService();
}
public int getEntityCount(String criteria) {
return cacheableService.cachedEntityCount(criteria);
}
}
And the same test but without Spring:
package com.example.withoutspring;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CaffeineCacheableTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private B b = new B();
#BeforeEach
public void init() {
System.setOut(new PrintStream(outContent));
}
#Test
public void testCaching() {
b.getEntityCount("foo");
b.getEntityCount("foo");
assertEquals("Inside function: foo", outContent.toString());
}
}
Obviously you need to tune the cache to perform how you want it so probably evicting the cached values after 5 minutes is not what you want but if you visit the Caffeine Github page you will see a lot of detailed examples how to configure the cache to meet your use-case.
Hope this helps!

Loading a random class using reflection and have it register as a component in springboot

I have a random class in a random package that is loaded through reflection after the app launches, is there a way for it to be registered as a component under springboot and have annotations such as #Autowired and #Value etc work for that class.
It works when it is in the same package at launch time, but if introduce it thorough another jar at runtime (same package or not) it doesn't work.
Below are samples that don't work even if it is in the same jar. I can't change the app's configuration - it would defeat the "random package/random class" objective.
Code in Spring boot application package
package sample.app
#SpringBootApplication
public class Application {
public static void main(String[] args) {
// Code that starts app
//
//
try {
Thread.sleep(7000);
Class test = Class.forName("test.Test", true, Application.class.getClassLoader());
System.out.println(test.getMethod("getName").invoke(null)); //NPE
System.out.println(test.getMethod("getProfiles").invoke(null)); //NPE
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Test.java
package test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.DependsOn;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
#DependsOn("blaaaaaaaah")
#ComponentScan
public class Test {
#DependsOn("blaaaaaaaah")
public static String getName() {
return SpringGetter.instance.getApplicationName();
}
#DependsOn("blaaaaaaaah")
public static String[] getProfiles() {
String[] profiles = SpringGetter.instance.getEnv().getActiveProfiles();
if (profiles == null || profiles.length == 0) {
profiles = SpringGetter.instance.getEnv().getDefaultProfiles();
}
return profiles;
}
}
SpringGetter.java
package test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
#Component("blaaaaaaaah")
public class SpringGetter implements InitializingBean {
public static SpringGetter instance;
#Value("${spring.application.name}")
private String applicationName;
#Autowired
private Environment env;
public SpringGetter() {
System.out.println("consASFJEFWEFJWDNFWJVNJSBVJWNCJWBVJNVJNVJSNJSNCSDJVNSVJtruct");
}
public String getApplicationName() {
return applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public Environment getEnv() {
return env;
}
public void setEnv(Environment env) {
this.env = env;
}
#PostConstruct
public void setInstance() {
instance = this;
}
#Override
public void afterPropertiesSet() throws Exception {
instance = this;
}
}
EDIT:
I managed to dynamically create the SpringGetter class as part of the same package as the Application class(the one with the #SpringBootApplication). I got Test.java to point to that dynamic class and yet no luck.
To simply inject fields into a POJO as if it were a Spring-managed bean, you can use something like the following:
#Component
public class BeanInitializer implements ApplicationContextAware {
private AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(final ApplicationContext applicationContext) {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
public void initializeObject(Object pojo) {
beanFactory.autowireBean(pojo);
}
}
Note, however, that this only injects fields marked as #Autowired or #Injected. It does not create proxies that honor method interception strategies based on e.g. #Transactional, #Async, etc.
If you're using Spring 5, have a look at the registerBean() method from GenericApplicationContext. You can find an example here: https://www.baeldung.com/spring-5-functional-beans
The issue in your Test class may also be that you're not loading the Spring Boot context from the main class. You can use the SpringBootTest annotation for this.

Adding elements to mocked list

I'm trying to unit test the method responsible for adding to map categorized books.
#Service
public class BookService {
private final List<BookServiceSource> sources;
#Autowired
public BookService(List<BookServiceSource> sources) {
this.sources = sources;
}
public Map<Bookstore, List<Book>> getBooksByCategory(CategoryType category) {
return sources.stream()
.collect(Collectors.toMap(BookServiceSource::getName,
source -> source.getBooksByCategory(category)));
}
}
BookSerivceSource is an interface. This interface is implemented by two classes. I'm gonna provide just one, as the second is really similiar.
EmpikSource (one of implementation)
package bookstore.scraper.book.booksource.empik;
import bookstore.scraper.book.Book;
import bookstore.scraper.book.booksource.BookServiceSource;
import bookstore.scraper.enums.Bookstore;
import bookstore.scraper.enums.CategoryType;
import bookstore.scraper.urlproperties.EmpikUrlProperties;
import bookstore.scraper.utilities.JSoupConnector;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.IntStream;
#Service
public class EmpikSource implements BookServiceSource {
private static final int FIRST_PART_PRICE = 0;
private static final int SECOND_PART_PRICE = 1;
private static final int BESTSELLERS_NUMBER_TO_FETCH = 5;
private static final int CATEGORIZED_BOOKS_NUMBER_TO_FETCH = 15;
private static final String DIV_PRODUCT_WRAPPER = "div.productWrapper";
private static final String DATA_PRODUCT_ID = "data-product-id";
private final EmpikUrlProperties empikUrlProperties;
private final JSoupConnector jSoupConnector;
private Map<CategoryType, String> categoryToEmpikURL;
#Autowired
public EmpikSource(EmpikUrlProperties empikUrlProperties, JSoupConnector jSoupConnector) {
this.empikUrlProperties = empikUrlProperties;
this.jSoupConnector = jSoupConnector;
categoryToEmpikURL = createCategoryToEmpikURLMap();
}
#Override
public Bookstore getName() {
return Bookstore.EMPIK;
}
#Override
public List<Book> getBooksByCategory(CategoryType categoryType) {
Document document = jSoupConnector.connect(categoryToEmpikURL.get(categoryType));
List<Book> books = new ArrayList<>();
List<Element> siteElements = document.select("div.productBox__info");
IntStream.range(0, CATEGORIZED_BOOKS_NUMBER_TO_FETCH)
.forEach(iteratedElement -> {
String author = executeFetchingAuthorProcess(siteElements, iteratedElement);
String price = convertEmpikPriceWithPossibleDiscountToActualPrice(siteElements.get(iteratedElement).select("div.productBox__price").first().text());
String title = siteElements.get(iteratedElement).select("span").first().ownText();
String productID = siteElements.get(iteratedElement).select("a").first().attr(DATA_PRODUCT_ID);
String bookUrl = createBookURL(title, productID);
books.add(Book.builder()
.author(author)
.price(price)
.title(title)
.productID(productID)
.bookURL(bookUrl)
.build());
});
return books;
}
private Map<CategoryType, String> createCategoryToEmpikURLMap() {
Map<CategoryType, String> map = new EnumMap<>(CategoryType.class);
map.put(CategoryType.CRIME, empikUrlProperties.getCrime());
map.put(CategoryType.BESTSELLER, empikUrlProperties.getBestSellers());
map.put(CategoryType.BIOGRAPHY, empikUrlProperties.getBiographies());
map.put(CategoryType.FANTASY, empikUrlProperties.getFantasy());
map.put(CategoryType.GUIDES, empikUrlProperties.getGuides());
map.put(CategoryType.MOST_PRECISE_BOOK, empikUrlProperties.getMostPreciseBook());
map.put(CategoryType.ROMANCES, empikUrlProperties.getRomances());
return map;
}
private String convertEmpikPriceWithPossibleDiscountToActualPrice(String price) {
String[] splittedElements = price.split("\\s+");
return splittedElements[FIRST_PART_PRICE] + splittedElements[SECOND_PART_PRICE];
}
private String createBookURL(String title, String productID) {
return String.format(empikUrlProperties.getConcreteBook(), title, productID);
}
//method is required as on empik site, sometimes occurs null for author and we need to change code for fetching
private static String executeFetchingAuthorProcess(List<Element> siteElements, int i) {
String author;
Element authorElements = siteElements.get(i).select("span > a").first();
if (authorElements != null)
author = authorElements.ownText();
else
author = siteElements.get(i).select("> span > span").first().text();
return author;
}
private String concatUrlWithTitle(String url, String title) {
return String.format(url, title);
}
}
JsoupConnector:
package bookstore.scraper.utilities;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.stereotype.Component;
import java.io.IOException;
#Component
public class JSoupConnector {
public Document connect(String url) {
try {
return Jsoup.connect(url).get();
} catch (IOException e) {
throw new IllegalArgumentException("Cannot connect to" + url);
}
}
}
Properties class:
package bookstore.scraper.urlproperties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
#Getter
#Setter
#Component
#ConfigurationProperties("external.library.url.empik")
public class EmpikUrlProperties {
private String mostPreciseBook;
private String bestSellers;
private String concreteBook;
private String romances;
private String biographies;
private String crime;
private String guides;
private String fantasy;
}
While debugging the test I see that the sources size is 0. How should I add mocked object to the sources list or could you tell me if there is better way to do this?
//EDIT
Forgot to paste the test :P
Test
package bookstore.scraper.book;
import bookstore.scraper.book.booksource.BookServiceSource;
import bookstore.scraper.book.booksource.empik.EmpikSource;
import bookstore.scraper.book.booksource.merlin.MerlinSource;
import bookstore.scraper.dataprovider.EmpikBookProvider;
import bookstore.scraper.dataprovider.MerlinBookProvider;
import bookstore.scraper.enums.Bookstore;
import bookstore.scraper.enums.CategoryType;
import bookstore.scraper.urlproperties.EmpikUrlProperties;
import bookstore.scraper.urlproperties.MerlinUrlProperties;
import bookstore.scraper.utilities.JSoupConnector;
import org.jsoup.nodes.Document;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.List;
import java.util.Map;
import static bookstore.scraper.dataprovider.MergedBestsellersMapProvider.prepareExpectedMergedBestSellerMap;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
#RunWith(MockitoJUnitRunner.class)
public class BookServiceTest {
#Mock
MerlinSource merlinSource;
#Mock
EmpikSource empikSource;
#Mock
BookServiceSource bookServiceSource;
#Mock
private EmpikUrlProperties empikMock;
#Mock
private MerlinUrlProperties merlinMock;
#Mock
JSoupConnector jSoupConnector;
#Mock
List<BookServiceSource> source;
#InjectMocks
BookService bookService;
#Test
public void getBooksByCategory() {
List<Book> merlinBestsellers = MerlinBookProvider.prepare5Bestsellers();
List<Book> empikBestsellers = EmpikBookProvider.prepare5Bestsellers();
Document empikDocument = mock(Document.class);
Document merlinDocument = mock(Document.class);
source.add(empikSource);
source.add(merlinSource);
when(bookServiceSource.getName()).thenReturn(Bookstore.EMPIK);
when(jSoupConnector.connect("https://www.empik.com/bestsellery/ksiazki")).thenReturn(empikDocument);
when(empikMock.getBestSellers()).thenReturn("https://www.empik.com/bestsellery/ksiazki");
when(empikSource.getBooksByCategory(CategoryType.CRIME)).thenReturn(empikBestsellers);
when(bookServiceSource.getName()).thenReturn(Bookstore.MERLIN);
when(jSoupConnector.connect("https://merlin.pl/bestseller/?option_80=10349074")).thenReturn(merlinDocument);
when(merlinMock.getBestSellers()).thenReturn("https://merlin.pl/bestseller/?option_80=10349074");
when(merlinSource.getBooksByCategory(CategoryType.CRIME)).thenReturn(merlinBestsellers);
Map<Bookstore, List<Book>> actualMap = bookService.getBooksByCategory(CategoryType.CRIME);
Map<Bookstore, List<Book>> expectedMap = prepareExpectedMergedBestSellerMap();
assertEquals(expectedMap, actualMap);
}
}
As mentioned before do not try to mock the List object.
Also generally avoid to create mocks for objects that you can simply create on your own and try to restrict yourself to mock only dependencies.
A simplified version of your test could look like this:
As your test covers quite a bit more than the Unit BookService
I decided to minimize it for this example.
You might want to do all the other stuff in a test for the specific implementation instead.
#Test
public void getBooksByCategory() {
List<Book> empikBestsellers = EmpikBookProvider.prepare5Bestsellers();
List<Book> merlinBestsellers = MerlinBookProvider.prepare5Bestsellers();
BookServiceSource bookServiceSource1 = Mockito.mock(BookServiceSource.class);
Mockito.when(bookServiceSource1.getName()).thenReturn(Bookstore.EMPIK);
Mockito.when(bookServiceSource1.getBooksByCategory(CategoryType.CRIME)).thenReturn(empikBestsellers);
BookServiceSource bookServiceSource2 = Mockito.mock(BookServiceSource.class);
Mockito.when(bookServiceSource2.getName()).thenReturn(Bookstore.MERLIN);
Mockito.when(bookServiceSource2.getBooksByCategory(CategoryType.CRIME)).thenReturn(merlinBestsellers);
List<BookServiceSource> sources = new ArrayList<>();
sources.add(bookServiceSource1);
sources.add(bookServiceSource2);
BookService service = new BookService(sources);
Map<Bookstore, List<Book>> actualMap = service.getBooksByCategory(CategoryType.CRIME);
// compare result
}
I don't believe you should be mocking the list of BookServiceSource since your adds will do nothing since it is not a real list.
This answer here should provide the information you are looking for: Mockito - Injecting a List of mocks
Edit for more clarity:
#InjectMocks should not be used if you can help it, it has a tendency to fail silently.
The other point I was attempting to make is that you are using a mocked list, and because of that when it is told to add elements it will not.
There are two solutions to the problem that you can use. Firstly you could create a when thenreturn for the stream of BookServiceSources, not the recommended solution.
Secondly what would be better is to create a testSetup method making use of the #Before annotation to create the BookService.
#Before
public void testSetup(){
List<BookServiceSource> list = new LinkedList<>();
list.add(merlinSource);
list.add(empikSource);
bookService = new BookService(list);
}
Try #Spy. It allows you to inject actual instance of a list that you have initialized by yourself and which also can be mocked partially.
#Spy
private List<BookServiceSource> sources = new ArrayList<>();
It seems that you have used different name for the List, prefer to use the smae name that field to mock is injected is; sources.
Good explanation here.
5. Mock vs. Spy in Mockito :
When Mockito creates a mock – it does so from the Class of a Type, not from an actual instance. The mock simply creates a bare-bones shell instance of the Class, entirely instrumented to track interactions with it.
On the other hand, the spy will wrap an existing instance. It will still behave in the same way as the normal instance – the only difference is that it will also be instrumented to track all the interactions with it.

What is a right way to initialize fields in Spring Beans?

I'm wondering how should I initialize fields in Spring Beans? Here is several possible solutions:
1. Initialize fields directly on declaration
import org.springframework.stereotype.Component;
#Component
public class DeclarationInit {
private final int field = Integer.MAX_VALUE;
public int getField() {
return field;
}
}
2. Initialize fields using #Value annotation
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class ValueInit {
#Value("#{T(Integer).MAX_VALUE}")
private int field;
public int getField() {
return field;
}
}
3. Initialize fields using #Autowired annotation
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class AutowiredInit {
private int field;
#Autowired
private void initField() {
field = Integer.MAX_VALUE;
}
public int getField() {
return field;
}
}
4. Initialize fields using #PostConstruct annotation
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
#Component
public class PostConstructInit {
private int field;
#PostConstruct
private void initField() {
field = Integer.MAX_VALUE;
}
public int getField() {
return field;
}
}
All tests succeeds and do not show any difference:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SomeTestContextConfiguration.class)
public class FieldInitTest {
#Autowired
private DeclarationInit declarationInit;
#Autowired
private ValueInit valueInit;
#Autowired
private AutowiredInit autowiredInit;
#Autowired
private PostConstructInit postConstructInit;
#Test
public void shouldInitializeFieldOnDeclaration() {
assertThat(declarationInit.getField(), equalTo(Integer.MAX_VALUE));
}
#Test
public void shouldInitializeFieldWithValueAnnotation() {
assertThat(valueInit.getField(), equalTo(Integer.MAX_VALUE));
}
#Test
public void shouldInitializeFieldWithAutowiredSetter() {
assertThat(autowiredInit.getField(), equalTo(Integer.MAX_VALUE));
}
#Test
public void shouldInitializeFieldWithPostConstruct() {
assertThat(postConstructInit.getField(), equalTo(Integer.MAX_VALUE));
}
}
Are this declarations equal to each other or should I use only one of them or neither of them?
Assuming the value is a constant, the first option is the simplest to understand and works without Spring, simplifying unit testing.
The second and fourth option are more complex and introduce an unnecessary dependency on the Spring container without any benefit. The third option is outright bizarre, since you're using #Autowired and not performing dependency injection.
I believe spring offers all those options because you might run into different requirements...
If you want MAX_INT and there's no way on earth anyone needs to initialize it differently, then it's enough to declare int field = Integer.MAX_INT regardless of Spring.
If you do want to allow other initial configurations, then you can initialize it using #Autowired, or through a constructor arg, or setter/getter... it's a matter of taste.
#PostConstruct is more suitable for complex situations, e.g. if your field needs to be calculated based on other injected fields.

Categories

Resources