Inject custom service to spring boot test - java

I have an interface I and 2+ of its implementations/services (ex. I1, I2)
public interface I {
void handle();
Network getNetwork();
}
and I have another one service (ex. IHolder) that is injecting the list of all services that implementing I and is putting it to its inner private map field where key is some unique Enum (interface has public method that returns one) and value the implementation itself.
public class IHolder {
private final Map<Network, I> iByNetwork = new HashMap<>();
public IHolder(List<I> is) {
for (I i : is) {
register(i.getNetwork(), i);
}
}
private void register(Network network, I i) {
this.iByNetwork.put(network, i);
}
public I getI(Network network) {
return iByNetwork.get(network);
}
}
Now I want to inject IHolder to my test class to use his map filled with those services

So basically annotate the IHolder class with #Service, but you won't be able to use it's private field (map), at least not in a simple way...
edit
Try to add these annotations to your test classes:
#RunWith(SpringRunner.class)
#SpringBootTest

Related

If we can use dataProvider using by extending class then why we are using dataProviderClass parameter in #Test annotation ? correct me if I wrong

// This is from another file but same package.
public class EmpData
{
#DataProvider
public Object[][] getData()
{
Object[][] data= {{"Hello","text","1"},{"bye","Message","32"},{"solo","call","678"}};
return data;
}
}
// Type -1 ( This is from another file but the same package.)
public class DPClass extends EmpData
{
#Test(dataProvider="getData")
public void testCaseData(String Greeting, String Communication, String Id)
{
System.out.println(Greeting+Communication+Id);
}
}
// Type -2
public class DPClass
{
#Test(dataProvider="getData", dataProviderClass=DPClass.class)
public void testCaseData(String Greeting, String Communication, String Id)
{
System.out.println(Greeting+Communication+Id);
}
}
In above there are two separate file DPClass.java and EmpData.java in one single package we can provide data to DPClass in both way either add parameter in #Test annotation and by extending class with adding parameter in #Test annotation. If we are achieving this by extending class then why TestNG provided DataProviderClass? Correct me if I wrong for this concept, I need difference for both.
That totally depends on how user needs to use it. If dataProviderClass option is not provided and if the user wants to keep data methods separate from the test methods, then the user is forced to extend the class as in your example.
public class DPClass {
#DataProvider
public Object[][] getData()
{
Object[][] data= {{"Hello","text","1"},{"bye","Message","32"},{"solo","call","678"}};
return data;
}
}
public class TestClass extends DPClass
{
#Test(dataProvider="getData")
public void testCaseData(String Greeting, String Communication, String Id)
{
System.out.println(Greeting+Communication+Id);
}
}
But imagine if your test class is already extending another class. So your test class cannot extend the DPClass (Classes in Java support single inheritance) and hence you can't keep data provider methods in a different class.
Example: For spring boot tests, if the test class requires spring application context, then the test class should extend AbstractTestNGSpringContextTests. So you cannot extend any more class.

Inject a Map of Interface Implementations Keyed by Enum Values

I am using Java 8 with a Play framework. My goal is to inject a map whose keys are enum values and values are implementations of a specific interface.
Here is my enum:
public enum Service {
HTML("html"), TEXT("txt");
private String serviceId;
Service(String serviceId) { this.serviceId = serviceId; }
}
I have Executable interface
public interface Executable { void execute(); }
and two classes that implement it:
public class HtmlWorker implements Executable { ... }
public class TextWorker implements Executable { ... }
I would like to be able to inject Map<Service, Executable> serviceMap so I can have access to a specific implementation using a Service key:
public class Processor {
#Inject
Map<Service, Executable> serviceMap;
public void doStuff() {
Executable htmlService = this.serviceMap.get(Service.HTML);
Executable textService = this.serviceMap.get(Service.TEXT);
// do more stuff
}
}
I added bindings to the module class:
public class AppModule extends AbstractModule {
#Override
protected void configure() {
MapBinder<Service, Executable> serviceBinder = MapBinder
.newMapBinder(binder(), Service.class, Executable.class);
serviceBinder.addBinding(Service.HtmlService).to(HtmlWorker.class);
serviceBinder.addBinding(Service.TextService).to(TextWorker.class);
}
The problem is that serviceMap is never injected and it is always null inside Processor. What am I missing?
According to the official MapBinder documentation the MapBinder.addBinding should take the map's key.
As far as concerning your provided example what about changing AbstractModule's code from:
serviceBinder.addBinding(Service.HtmlService).to(HtmlWorker.class);
serviceBinder.addBinding(Service.TextService).to(TextWorker.class);
to
serviceBinder.addBinding(Service.HTML).to(HtmlWorker.class); // <-- see the enum constant here?
serviceBinder.addBinding(Service.TEXT).to(TextWorker.class);
Anyway I don't know where the class Service.HtmlService in your example comes from since you didn't state it anywhere.

How to delegate to services by class type?

I have different class types, and depending on some conditions, I want to delegate to the appropriate service that can handle those class types.
Example:
I have several classes as follows.
class Student;
class Prof;
...
For each class there is a service, implementing:
interface IPersonService {
void run();
}
And I have a mode that is found by some conditions:
enum PersonType {
STUDENT, PROF;
}
When I delegate:
#Autowired
private StudentService studentService;
#Autowired
private ProfService profService;
//#param mode assume known
public void delegate(PersonType mode) {
//assume there are several of those switch statements in my business code
switch (mode) {
case STUDENT: studentService.run(); break;
case PROF: profService.run(); break;
default: break;
}
}
Problem: When introducing additional classes, I have to both modify the PersonType and add an additional enum (which is no problem), but I also have to extend any switch statement and add calls to additional delegation services. Also I have to explicit autowire those services to the switch delegator.
Question: how could I optimize this code, to just implementing new Services for any additional class, and not having to touch any of the switch statements?
Add a method to IPersonService so that the implementation of the method can tell the program what type of persons it handles:
interface IPersonService {
PersonType supportedPersonType();
void run();
}
In the service that does the delegation, inject a List<IPersonService>, which Spring will fill with all the implementations of IPersonService that it can find. Then implement the delegate method to look through the list to find the first IPersonService that can handle the specific type.
#Autowired
private List<IPersonService> personServices;
public void delegate(PersonType mode) {
for (IPersonService personService : personServices) {
if (personService.supportedPersonType().equals(mode)) {
personService.run();
break;
}
}
}
This way, you can add new implementations of IPersonService without having to change the service that does the delegation.
To avoid having to go through the loop each time delegate is called, you could build a Map beforehand so that the right IPersonService can be looked up quickly:
class DelegatingService {
#Autowired
private List<IPersonService> personServices;
private Map<PersonType, IPersonService> personServiceMap;
#PostConstruct
public void init() {
personServiceMap = new HashMap<>();
for (IPersonService personService : personServices) {
personServiceMap.put(personService.supportedPersonType(), personService);
}
}
public void delegate(PersonType mode) {
personServiceMap.get(mode).run();
}
}
(Error handling omitted for simplicity).
In my application, we solved similar problem, by putting services into a map. Consider Map<PersonType,IPersonService> serviceMap defined as bean and injected into your class.
Then delegate method simple do
public void delegate(PersonType mode) {
IPersonService service = serviceMap.get(mode);
if (service!=null){
service.run();
}else{
//do something if service is null
}
}
You can store the service bean name (or class type) in the enum, and fetch the beans from the application context using getBean by name (or by class type respectively).
Also, all the services will need to implement a interface which has the run method.
interface ModeService {
void run();
}
enum PersonType {
STUDENT("studentService"), PROF("profService");
private String serviceBean;
public PersonType(String serviceBean) {
this.serviceBean = serviceBean);
}
public String getServiceBean() {
return serviceBean;
}
}
in delegate then the following can be used. ((ModeService)applicationContext.getBean(mode.getServiceBean()).run()
This way only the enum needs to be updated with the service type that is to be used, and no change to delegate method is required.

What is the best approach to get injected beans with same interface in factory using Spring?

I created one factory to decide what best implementation should be returned, based in some conditional check.
// Factory
#Component
public class StoreServiceFactory {
#Autowired
private List<StoreService> storeServices;
public StoreService getService(){
if(isActiveSale){
return storeServices.get("PublicStoreService")
}
return storeServices.get("PrivateStoreService")
}
}
//Service Implementations
#Service
#Qualifier("PublicStoreService")
public class PublicStoreService implements StoreService {
public getStoreBalanceScore(){
Do Stuff....
}
}
#Service
#Qualifier("PrivateStoreService")
public class PrivateStoreService implements StoreService {
public getStoreBalanceScore(){
Do Stuff....
}
}
// Controller
#Autowired
StoreServiceFactory storeServiceFactory;
#Override
public StoreData getStoreBalance(String storeId) {
StoreService storeService = storeServiceFactory.getService();
return simulationService.simulate(sellerId, simulation);
}
Is this approach good? If yes, how can i get my service from an elegant way?
I would like to use only annotations, without configurations.
You should use a map instead of a List and pass a string parameter to the getService method.
public class StoreServiceFactory {
#Autowired
private Map<String,StoreService> storeServices = new HashMap<>();
public StoreService getService(String serviceName){
if(some condition...){
// want to return specific implementation on storeServices map, but using #Qualifier os something else
storeServices.get(serviceName)
}
}
}
You can prepopulate the map with supported implementations. You can then get an appropriate service instance as follows :
// Controller
#Autowired
StoreServiceFactory storeServiceFactory;
#Override
public StoreData getStoreBalance(String storeId) {
StoreService storeService = storeServiceFactory.getService("private");//not sure but you could pass storeId as a parameter to getService
return simulationService.simulate(sellerId, simulation);
}
If you don't like using Strings, you can define an enum for the supported implementations and use that as the key for your map.
You don't need to create a list or map on your code. You can retrieve it directly from Spring context using GenericBeanFactoryAccessor. This has various method to retrieve a specific bean like based on name, annotation etc. You can take a look at javadoc here. This avoids unnecessary complexity.
http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/beans/factory/generic/GenericBeanFactoryAccessor.html

How to inject an interface implementation based on annotations at runtime using Google Guice

I have the following scenario:
public interface ServiceClientAdapter {
SomeData getSomeData()
}
#LegacyServiceClientAdapter
public class MyLegacyServiceClientAdapterImpl implements ServiceClientAdapter {
public SomeData getSomeData() {
// implementation
}
}
#NewServiceClientAdapter
public class MyNewServiceClientAdapterImpl implements ServiceClientAdapter {
public SomeData getSomeData() {
// implementation
}
}
public class BusinessLogic {
#Inject
private ServiceClientAdapter serviceClientAdapter;
}
LegacyServiceClientAdapter and NewServiceClientAdapter are custom annotations.
The implementation for the serviceClientAdapter field will be determined at runtime by whether the user has been migrated from the legacy to the new service or not.
What is the best way to accomplish this dependency injection using Google Guice?
Take into account that different BusinessLogic classes will exist, each with their own (different) ServiceClientAdapter-like interface and corresponding legacy and new implementation classes.
Ideally this should be done with a piece of framework code that can be used across all use cases.
I'm going to assume that the result of your LDAP call can be represented as a string, let's say "legacy" or "new". If not, hopefully you should still be able to adapt this example.
In your module, use a MapBinder:
public class BusinessLogicModule {
#Override
protected void configure() {
// create empty map binder
MapBinder<String, ServiceClientAdapter> mapBinder =
MapBinder.newMapBinder(
binder(), String.class, ServiceClientAdapter.class);
// bind different impls, keyed by descriptive strings
mapBinder.addBinding("legacy")
.to(MyLegacyServiceClientAdapterImpl.class);
mapBinder.addBinding("new")
.to(MyNewServiceClientAdapterImpl.class);
}
}
Now you can inject a map of instances (or a map of providers of instances if you need to keep creating new instances) into your main class and use the string discovered at runtime to control which kind of instance you get.
public class BusinessLogic {
#Inject
private ServiceClientAdapter serviceClientAdapter;
#Inject
private Map<String, ServiceClientAdapter> mapBinder;
public void setupAndUseClientAdapter() {
String userType = getUserTypeFromLdapServer();
serviceClientAdapter = mapBinder.get(userType);
if (serviceClientAdapter == null) {
throw new IllegalArgumentException(
"No service client adapter available for " +
userType + " user type.";
}
doStuffWithServiceClientAdapter();
}
}

Categories

Resources