I wrote a JUnit 5 test for my service in my Spring Boot application.
I used #MockBean to mock PasswordEncoder and other beans but I obtain a NullPointerException.
I always obtain a NullPointerException during the when call :
when(compteRepository.getByLogin(anyString())).thenReturn(Optional.of(acc));
Service
package com.compte.application.impl;
import com.compte.application.CompteService;
import com.compte.domain.exceptions.EntityNotFoundExcpetion;
import com.compte.domain.model.Compte;
import com.compte.domain.model.CompteUpdatedData;
import com.compte.domain.repository.CompteRepository;
import com.compte.domain.utils.CompteUtil;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.time.LocalDate;
import java.util.Optional;
/**
* #author mbint
*/
#AllArgsConstructor
public class CompteServiceImpl implements CompteService{
private final static Logger LOGGER = LoggerFactory.getLogger(CompteService.class);
private CompteRepository CompteRepository;
private PasswordEncoder passwordEncoder;
#Override
public Optional<Compte> getByLogin(String login) {
return CompteRepository.getByLogin(login);
}
#Override
public void update(final Long id, CompteUpdatedData updatedData) {
Optional<Compte> optional = CompteRepository.getById(id);
if(optional.isPresent()) {
Compte Compte = optional.get();
Compte.setFirstName(updatedData.getFirstName());
Compte.setLastName(updatedData.getLastName());
CompteRepository.save(Compte);
} else {
throw new EntityNotFoundExcpetion("Compte: " + id + " not found !!");
}
}
}
Junit Test
package com.compte.application;
import com.compte.application.impl.CompteServiceImpl;
import com.compte.domain.model.Compte;
import com.compte.domain.model.CompteUpdatedData;
import com.compte.domain.repository.compteRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static org.mockito.Mockito.*;
/**
* #author mbint
*/
public class CompteServiceImplTest {
private final static String PASSWORD = "Passw00rd";
#MockBean
private compteRepository compteRepository;
#MockBean
private PasswordEncoder passwordEncoder;
private CompteService CompteService = new CompteServiceImpl(compteRepository, passwordEncoder);
#DisplayName(("Should return existing user"))
#Test
private void given_login_then_return_existing_user() {
Compte acc = Compte.builder().id(1L)
.firstName("Luc")
.lastName("JOJO")
.login("xxx#gmail.com")
.password("xxxxxxxxxxxxxxx")
.build();
when(compteRepository.getByLogin(anyString())).thenReturn(Optional.of(acc));
Optional<Compte> optional = CompteService.getByLogin("xxx#gmail.com");
Compte Compte = optional.get();
Assertions.assertSame(1L, acc.getId());
Assertions.assertSame("xxx#gmail.com", Compte.getLogin());
}
#DisplayName("Should update existing user")
#Test
public void given_edited_Compte_then_update_user() {
Compte acc = Compte.builder().id(1L)
.firstName("Luc")
.lastName("JOJO")
.email("xxx#gmail.com")
.password("xxxxxxxxxxxxxxx")
.build();
when(compteRepository.getById(anyLong())).thenReturn(Optional.of(acc));
CompteUpdatedData updatedData = CompteUpdatedData.builder()
.firstName("Moos")
.lastName("Man")
.build();
CompteService.update(1L, updatedData);
Assertions.assertSame("Moos", acc.getFirstName());
}
private List<Compte> getComptes() {
List<Compte> Comptes = new ArrayList<>();
Compte acc1 = Compte.builder()
.id(1L)
.firstName("Luc")
.lastName("JOJO")
.email("xxx#gmail.com")
.login("xxx#gmail.com")
.build();
Comptes.add(acc1);
Compte acc2= Compte.builder()
.id(2L)
.firstName("Jean")
.lastName("KELLY")
.email("jean.kelly#gmail.com")
.login("jean.kelly#gmail.com")
.build();
Comptes.add(acc2);
Compte acc3= Compte.builder()
.id(3L)
.firstName("Marc")
.lastName("BARBY")
.email("marc.barby#gmail.com")
.login("marc.barby#gmail.com")
.build();
Comptes.add(acc3);
return Comptes;
}
}
Spring boot application
package com.compte;
import com.compte.application.CompteService;
import com.compte.application.impl.CompteServiceImpl;
import com.compte.domain.repository.CompteRepository;
import com.compte.infrastructure.repository.database.CompteDBRepositiry;
import com.ombsc.bargo.common.config.SwaggerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.hateoas.client.LinkDiscoverer;
import org.springframework.hateoas.client.LinkDiscoverers;
import org.springframework.hateoas.mediatype.collectionjson.CollectionJsonLinkDiscoverer;
import org.springframework.plugin.core.SimplePluginRegistry;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.ArrayList;
import java.util.List;
#ComponentScan({"com.compte.interfaces.interfaces"})
#SpringBootApplication
#Import({SwaggerConfig.class})
public class CompteApplication {
public static void main(String[] args) {
SpringApplication.run(CompteApplication.class, args);
}
#Bean
public CompteRepository getRepository() {
return new CompteDBRepositiry();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public CompteService CompteService(CompteRepository repository, PasswordEncoder passwordEncoder) {
return new CompteServiceImpl(repository, passwordEncoder);
}
#Bean
public LinkDiscoverers discovers() {
List<LinkDiscoverer> plugins = new ArrayList<>();
plugins.add(new CollectionJsonLinkDiscoverer());
return new LinkDiscoverers(SimplePluginRegistry.create(plugins));
}
}
The mocks need to be initialized before they can be used. There are several options to do this.
The first option would be to use #SpringExtension which will initialize the mocks annotated with #MockBean:
#ExtendWith(SpringExtension.class)
public class CompteServiceImplTest {
#Autowired
private CompteService CompteService;
#MockBean
private compteRepository compteRepository;
// ...
}
This will make sure that the repository bean is mocked before the service bean is autowired.
However, since you are writing a unit test for the service, you don't need the Spring extension at all. The second option is to use #Mock instead of #MockBean, and call #InjectMocks in conjunction with the MockitoExtension for constructing the service under test:
#ExtendWith(MockitoExtension.class)
public class CompteServiceImplTest {
#InjectMocks
private CompteService CompteService;
#Mock
private compteRepository compteRepository;
// ...
}
Alternatively, you could just call MockitoAnnotations.initMocks(), which will initialize the mocks annotated with #Mock, and use constructor injection for your service:
public class CompteServiceImplTest {
private CompteService CompteService;
#Mock
private compteRepository compteRepository;
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
CompteService = new CompteServiceImpl(compteRepository, ...);
}
// ...
}
Finally, you could do it all without annotations just by calling Mockito.mock() directly:
public class CompteServiceImplTest {
private compteRepository compteRepository;
#BeforeEach
void setUp() {
compteRepository = Mockito.mock();
CompteService = new CompteServiceImpl(compteRepository, ...);
}
// ...
}
Related
EDIT: One problem was that Tracing.current() was null. I fixed this with the new #BeforeEach instead of the old #BeforeTestMethod:
Tracer tracer;
#BeforeEach
void init() {
tracer = Tracing.newBuilder().build().tracer();
TraceContext ctx = TraceContext.newBuilder().traceId(17).spanId(17).build();
Span span = tracer.toSpan(ctx);
tracer.withSpanInScope(span);
}
Yet, updateValue still doesn't work as there are no extras in the context, so nothing to update...
Following the ideas like in those answers, I'm trying to use BaggageFields in one of my tests. All the objects are not null, but updateValue returns false and the test fails with BaggageTest.baggageTest:40 expected: <hello> but was: <null>. As said, I have no idea why the updateValue method is not working.
import static org.junit.jupiter.api.Assertions.assertEquals;
import brave.baggage.BaggageField;
import brave.baggage.CorrelationScopeConfig;
import brave.context.slf4j.MDCScopeDecorator;
import brave.propagation.CurrentTraceContext;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.ScopedSpan;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.event.annotation.BeforeTestMethod;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#Slf4j
#ExtendWith(SpringExtension.class)
#ContextConfiguration
public class BaggageTest {
#Autowired
ApplicationContext context;
#BeforeTestMethod
void init() {
Tracer tracer = context.getBean(Tracer.class);
ScopedSpan span = tracer.startScopedSpan("test");
}
#Test
void baggageTest() {
BaggageField fooBar = context.getBean("fooBar", BaggageField.class);
log.info("updateValue {}", fooBar.updateValue("hello"));
assertEquals("hello", fooBar.getValue());
}
#Configuration
static class Config {
private BaggageField findOrCreate(String name) {
BaggageField field = BaggageField.getByName(name);
if (field == null) {
field = BaggageField.create(name);
}
return field;
}
#Bean("fooBar")
BaggageField fooBar() {
return findOrCreate("fooBar");
}
#Bean
CurrentTraceContext.ScopeDecorator mdcScopeDecorator() {
return MDCScopeDecorator.newBuilder()
.clear()
.add(CorrelationScopeConfig.SingleCorrelationField.newBuilder(fooBar())
.flushOnUpdate()
.build())
.build();
}
}
}
My solution is to manually build the TraceContext, using the following snippet, being careful that BaggageFields is brave.internal.baggage.BaggageFields.
#Autowired
BaggageField field;
Tracer tracer;
#BeforeEach
void init() {
tracer = Tracing.newBuilder().build().tracer();
ArrayList<BaggageField> list = new ArrayList<>();
list.add(field);
TraceContext ctx = TraceContext.newBuilder()
.addExtra(BaggageFields.newFactory(list,2).create())
.traceId(17).spanId(17).build();
Span span = tracer.toSpan(ctx);
tracer.withSpanInScope(span);
}
I am trying to run tests on a controller class and it has atleast a method which internally uses a DAO to retrieve information from the DB (MySQL).
The problem I have is that the dao method is giving null and I end up with NullPointerException error.
How do I tests a class which has methods that internally use a database connection?
Haven't been able to find any useful post/answer.
Project structure:
src
main
java
[package]
RegisterController.java
Config.java // configuration class
test
java
[package]
RegisterControllerTest.java
RegisterController.java
package com.webapp.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import com.webapp.Config;
import com.webapp.dao.AccountDao;
#Controller
public class RegisterController {
#Autowired
AccountDao accDao;
public String validateUsername(String uname){
List<String> errors = new ArrayList<String>();
// ... unrelated code
// NullPointerException thrown here
if(accDao.getAccountByUsername(uname) != null)
errors.add("err#taken");
return errors.toString();
}
}
RegisterControllerTest.java
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import com.webapp.controller.RegisterController;
import com.webapp.Config;
#SpringBootTest
public class RegisterControllerTest {
#Mock
private Config config;
private RegisterController rc;
#BeforeEach
public void init() {
config = Mockito.mock(Config.class);
rc = new RegisterController();
}
#Test
public void testValidateUsername() {
assertEquals("[]", rc.validateUsername("Username123")); // N.P.E
}
}
Config.java
package com.webapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import com.webapp.dao.AccountDao;
import com.webapp.dao.AccountDaoImpl;
import com.webapp.dao.Dao;
#Configuration
#ComponentScan(basePackages = { "com.webapp.controller", "com.webapp.dao", "com.webapp.test" })
public class Config {
private static class Database {
private static String host = "127.0.0.1";
private static String user = "root";
private static String pass = "root";
private static String dbname = "memedb";
private static int port = 3306;
public static String getUrl() {
return "jdbc:mysql://"+host+":"+port+"/"+dbname+"?serverTimezone=Europe/Stockholm";
}
}
#Bean
public DriverManagerDataSource getDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl(Database.getUrl());
ds.setUsername(Database.user);
ds.setPassword(Database.pass);
return ds;
}
#Bean
public AccountDao getAccDao() {
return new AccountDaoImpl(getDataSource());
}
}
Instead of mocking config, mock the Dao. You are getting NPE, because the mocked config is not setting #Autowired accDao... so your accDao == null:
#Controller
public class RegisterController {
AccountDao accDao;
public RegisterController(AccountDao accDao) {
this.accDao = accDao;
}
...
}
#BeforeEach
public void init() {
accDaoMock = Mockito.mock(AccountDao.class);
rc = new RegisterController(accDaoMock);
}
#Test
public void testValidateUsername() {
when(accDaoMock.getAccountByUsername("Username123")).thenReturn(null);
assertEquals("[]", rc.validateUsername("Username123"));
}
Why are you configuring the connection DB programmatically? I advise you configure the connection DB with autoconfiguration of Spring Boot.
The DAO object have to mock in an unit test.
Here is a good article about the Junit test for a Spring Boot App.
I have an annotation that pulls in some configurations (via #Import). I want to test running with and without that annotation. I want to do this in one test class.
I know that I can change the spring context per method #DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD), but I don't know how to have it run with or without the annotation on different methods.
How would I achieve this?
What I want to do:
package com.test.reference.cors;
import com.test.EnableCors;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(secure = false)
#ContextConfiguration(classes = {ControllerConfig.class, ReferenceController.class})
public class TestCORS
{
private MockMvc mockMvc;
private ObjectMapper objectMapper;
#Autowired
private RestTemplate restTemplate;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setup()
{
//Create an environment for it
mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.dispatchOptions(true).build();
//Create our marshaller
objectMapper = new ObjectMapper();
}
#Test
public void testWithoutCors() throws Exception
{
//Call to test a date
MvcResult result = mockMvc.perform(
options("/v1/testdate")
.contentType(MediaType.APPLICATION_JSON)
//CORS HEADERS
.header("Access-Control-Request-Method", "DELETE")
.header("Origin", "https://evil.com")
).andExpect(status().isForbidden())
.andReturn();
}
#Test
#EnableCors
public void testWithCors() throws Exception
{
//Call to test a date
MvcResult result = mockMvc.perform(
options("/v1/testdate")
.contentType(MediaType.APPLICATION_JSON)
//CORS HEADERS
.header("Access-Control-Request-Method", "POST")
.header("Origin", "http://evil.com")
).andExpect(status().isOk())
.andReturn();
}
}
Using nested classes, you could do something like this:
class NewFeatureTest {
#SpringBootTest
protected static class NewFeatureWithDefaultConfigTest {
#Autowired
ApplicationContext context;
#Test
void yourTestMethod() {
}
#Configuration(proxyBeanMethods = false)
#EnableAutoConfiguration
protected static class Config {
// your 1st config
}
}
#SpringBootTest
protected static class NewFeatureWithDefaultsTests {
#Autowired
ApplicationContext context;
#Test
void yourOtherTestMethod() {
}
#Configuration(proxyBeanMethods = false)
#EnableAutoConfiguration
protected static class NoDefaultsConfig {
// your 2nd config
}
}}
I'm fairly new to Spring (the Neo4j side), and I am having trouble #AutoWire-ing my repository.
this is my repo:
package org.jarivm.relationGraph.objects.repositories;
public interface EmployeeRepository extends GraphRepository<Employee> {
#Query("MATCH a=(:Employee)-[:WORKED_ON]->(p:Project) WHERE id(p)={0} RETURN a")
Iterable<Employee> getTeamMates(Project client);
}
my test class:
package org.jarivm.relationGraph;
import org.apache.commons.collections4.set.ListOrderedSet;
import org.jarivm.relationGraph.objects.domains.Employee;
import org.jarivm.relationGraph.objects.domains.Project;
import org.jarivm.relationGraph.objects.repositories.EmployeeRepository;
import org.jarivm.relationGraph.utilities.NodeProperties;
import org.junit.After;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Iterator;
/**
* #author Jari Van Melckebeke
* #since 02.09.16
*/
#FixMethodOrder(MethodSorters.JVM)
public class Tests extends Application {
#Autowired
private Facade facade;
#Autowired
private EmployeeRepository employeeRepository;
#Before
public void setUp() throws Exception {
facade = new Facade();
}
#After
public void tearDown() throws Exception {
facade.tearDown();
}
/*
#Test
public void persistedEmployeeShouldBeRetrievableFromGraphDB() {
Employee employee = new Employee("john", "adams");
//System.out.println(session.getTransaction().status());
if (!facade.findEmployeeByProperty("name", employee.getName()).iterator().hasNext()) {
facade.commit(employee);
Employee foundHim = facade.findEmployeeByProperty("name", employee.getName()).iterator().next();
assert foundHim.getId().equals(employee.getId());
assert foundHim.getName().equals(employee.getName());
}
}
#Test
public void persistedChainShouldBeRetrievableFromGraphDB() {
Employee employee = new Employee("john", "myles");
Client client = new Client();
Sector sector = new Sector();
Project project = new Project();
client.setName("Real Dolmen");
project.setClient(client);
project.setCost(100.0);
project.setName("project highrise");
Set<Employee> set = new ListOrderedSet<Employee>();
set.add(employee);
project.setTeam(set);
sector.setName("game");
client.setSector(sector);
facade.commit(sector);
facade.commit(employee);
facade.commit(client);
facade.commit(project);
Client foundHim = facade.findClientByProperty("name", client.getName()).iterator().next();
assert foundHim.getId().equals(client.getId());
assert foundHim.getName().equals(client.getName());
}
#Test
public void projectShouldBeInsertableAlone() {
Project project = new Project();
project.setName("random");
project.setLanguage("Java");
facade.commit(project);
Project foundHim = facade.findProjectByProperty("name", project.getName()).iterator().next();
assert foundHim.getId().equals(project.getId());
}
#Test
public void clientShouldBeInsertableAlone() {
Client client = new Client();
client.setName("Colruyt");
facade.commit(client);
Client foundHim = facade.findClientByProperty("name", client.getName()).iterator().next();
assert foundHim.getId().equals(client.getId());
}*/
#Test
public void createdNodesShoudBeEditable() {
Iterator<Employee> employees = facade.findEmployeeByProperty("name", "john").iterator();
Project project = facade.findProjectByProperty("name", "random").iterator().next();
while (employees.hasNext()) {
Employee e = employees.next();
if (project.getTeam() == null)
project.setTeam(new ListOrderedSet<Employee>());
project.getTeam().add(e);
}
facade.commit(project);
}
package org.jarivm.relationGraph;
#Autowired
private EmployeeRepository employeeRepository;
#Test
public void teamMatesShouldBeViewable() {
Project p = facade.findProjectByProperty("name", "Matsoft").iterator().next();
System.out.println(p);
System.out.println(employeeRepository);
Iterable<Employee> e = employeeRepository.getTeamMates(p);
System.out.println(e.iterator());
}
}
and my Application.java class:
package org.jarivm.relationGraph;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* #author Jari Van Melckebeke
* #since 23.09.16
*/
#EnableTransactionManagement
#ComponentScan(basePackages = {"org.jarivm.relationGraph"})
#Configuration
#EnableNeo4jRepositories(basePackages = "org.jarivm.relationGraph.objects.repositories.EmployeeRepository")
public class Application extends Neo4jConfiguration {
public static final String URL = System.getenv("NEO4J_URL") != null ? System.getenv("NEO4J_URL") : "http://localhost:7474";
#Bean
public org.neo4j.ogm.config.Configuration getConfiguration() {
org.neo4j.ogm.config.Configuration config = new org.neo4j.ogm.config.Configuration();
config
.driverConfiguration()
.setDriverClassName("org.neo4j.ogm.drivers.http.driver.HttpDriver")
.setURI(URL)
.setCredentials("neo4j", "mypassword");
return config;
}
#Override
public SessionFactory getSessionFactory() {
return new SessionFactory(getConfiguration(), "org.jarivm.relationGraph.objects.domains");
}
}
The #autowire did never work with this program so I do not know what the problem is...
thank's in advance,
Jari Van Melckebeke
I think your Tests class should not extend Application, but instead be annotated with RunsWith - something like (untested):
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=org.jarivm.relationGraph.Application.class, loader=AnnotationConfigContextLoader.class
public class Tests {
for more information, see the section titled Integration Testing with #Configuration Classes:
https://spring.io/blog/2011/06/21/spring-3-1-m2-testing-with-configuration-classes-and-profiles
I'm trying to put together an SDK that uses Spring internally through a context it manages of its own. I want the jar that gets built to be usable regardless of whether or not Spring is in use on the application that wants to use the SDK.
I have something that works when it is running on its own. However if I attempt to use the SDK inside another Spring context (in my case a Spring Boot based application) I get a org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type exception.
Try as I might I cannot understand how to get this working, or indeed what I am doing wrong. The classes below show what I'm doing, the org.example.testapp.MySDKTest fails with the exception while the org.example.test.MySDKTest successfully passes. Sorry there is so much code but I can't reproduce the issue with a simplified case.
SDK source
package org.example.mysdk;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.example.mysdk.MyService;
import org.example.mysdk.MyServiceConfiguration;
public final class MySDK {
private static ApplicationContext applicationContext;
public static <T extends MyService> T getService(Class<? extends MyService> clazz, MyServiceConfiguration configuration) {
T tmp = (T) getApplicationContext().getBean(clazz);
tmp.setConfiguration(configuration);
return tmp;
}
private static ApplicationContext getApplicationContext() {
if (applicationContext == null) {
applicationContext = new AnnotationConfigApplicationContext(SpringContext.class);
}
return applicationContext;
}
}
.
package org.example.mysdk;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class MyService {
private MyServiceConfiguration configuration;
#Autowired
private MyAutowiredService myAutowiredService;
MyService() {
}
MyService(MyServiceConfiguration configuration) {
super();
this.configuration = configuration;
}
public MyServiceConfiguration getConfiguration() {
return configuration;
}
void setConfiguration(MyServiceConfiguration configuration) {
this.configuration = configuration;
}
String getSomething(String in) {
return "something + " + myAutowiredService.getThing(configuration.getValue()) + " and " + in;
}
}
.
package org.example.mysdk;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
#Service
#Scope("prototype")
public class MyServiceImpl1 extends MyService {
public MyServiceImpl1() {
}
public MyServiceImpl1(MyServiceConfiguration configuration) {
super(configuration);
}
public String method1() {
return this.getSomething("method1");
}
}
.
package org.example.mysdk;
public class MyServiceConfiguration {
private String value;
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
.
package org.example.mysdk;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
#Service
public class MyAutowiredService {
private String thing = "a value";
public String getThing(String in) {
return thing + " " + in;
}
#PostConstruct
void init() {
System.out.println("MyAutowiredService bean created");
}
}
.
package org.example.mysdk;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = {
"org.example.mysdk"
})
public class SpringContext {
}
Tests
This first test fails with a org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type exception,
package org.example.testapp;
import static org.junit.Assert.*;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = App.class, loader = AnnotationConfigContextLoader.class)
public class MySDKTest {
#Autowired
MyServiceImpl1 service;
#Test
public void test() {
MyServiceConfiguration conf = service.getConfiguration();
assertEquals(conf.getValue(), "this is the instance configuration");
}
}
.
package org.example.testapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.example.mysdk.MySDK;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
#Configuration
#ComponentScan(basePackages = {
"org.example.testapp"
})
public class App {
#Bean
public MyServiceImpl1 myServiceImpl1() {
MyServiceConfiguration configuration = new MyServiceConfiguration();
configuration.setValue("this is the instance configuration");
return MySDK.getService(MyServiceImpl1.class, configuration);
}
}
and this test succeeds,
package org.example.test;
import static org.junit.Assert.*;
import org.example.mysdk.MySDK;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
import org.junit.Test;
public class MySDKTest {
#Test
public void test() {
MyServiceConfiguration configuration = new MyServiceConfiguration();
configuration.setValue("this is the instance configuration");
MyServiceImpl1 service = MySDK.getService(MyServiceImpl1.class, configuration);
assertEquals(service.getConfiguration().getValue(), "this is the instance configuration");
}
}
If I've gone about this the completely wrong way I'm happy to hear suggestions of how this should be done differently!
You have to modify two files.
First App.java, it should scan for "org.example.mysdk" package to inject myAutowiredService in abstract class MyService, If not it has to be created in App.java. And the name of the MyServiceImpl1 bean must be different from myServiceImpl1 as it will conflict.
package org.example.testapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.example.mysdk.MySDK;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
#Configuration
#ComponentScan(basePackages = {
"org.example.testapp", "org.example.mysdk"
})
public class App {
#Bean
public MyServiceImpl1 myServiceImpl() {
MyServiceConfiguration configuration = new MyServiceConfiguration();
configuration.setValue("this is the instance configuration");
return MySDK.getService(MyServiceImpl1.class, configuration);
}
}
Then secondly in MySDKTest.java should inject myServiceImpl which was created in App.java
import static org.junit.Assert.*;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = App.class, loader = AnnotationConfigContextLoader.class)
public class MySDKTest {
#Autowired
MyServiceImpl1 myServiceImpl;
#Test
public void createOxiAccountService() {
MyServiceConfiguration conf = myServiceImpl.getConfiguration();
assertEquals(conf.getValue(), "this is the instance configuration");
}
}