I have a controller like this
#Path("/")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class AccountController implements CRUDController<Long, Account> {
private AccountDao accountDao;
private AccountService accountService;
#Inject
public AccountController(AccountDao accountDao, AccountService accountService) {
this.accountDao = accountDao;
this.accountService = accountService;
}
...
I'm injecting AccountDao and AccountService using
ResourceConfig config = new ResourceConfig()
.packages("controller", "exception")
.register(new MyDIBinder());
Where MyDIBinder is contains all the bindings (e.g
AccountDaoImpl accountDaoImpl = new AccountDaoImpl();
bind(accountDaoImpl).to(AccountDao.class);
)
Now I want to write a unit test for this controller, is it possible to inject the whole AccountController instance with all of it's transitive dependencies into the test?
Something like
#Inject
AccountController accountController;
You can use the main IoC container, and just explicitly inject the test class. Jersey uses HK2 as its DI framework, and its IoC container is the ServiceLocator, which has a method inject(anyObject) that can inject any objects with dependencies that are in its registry.
For example you could do something like
public class InjectionTest {
#Inject
private TestController controller;
#Before
public void setUp() {
final Binder b = new AbstractBinder() {
#Override
public void configure() {
bindAsContract(TestController.class);
}
};
final ServiceLocator locator = ServiceLocatorUtilities.bind(new TestBinder(), b);
locator.inject(this);
}
#Test
public void doTest() {
assertNotNull(controller);
String response = controller.get();
assertEquals("Hello Tests", response);
}
}
The ServiceLocatorUtilities class is a helper class that allows us to easily create the ServiceLocator, and then we just call inject(this) to inject the InjectionTest.
If it seems repetitive to do this for all your controller tests, you may want to create an abstract base test class. Maybe something like
public abstract class AbstractControllerTest {
protected ServiceLocator locator;
private final Class<?> controllerClass;
protected AbstractControllerTest(Class<?> controllerClass) {
this.controllerClass = controllerClass;
}
#Before
public void setUp() {
final AbstractBinder binder = new AbstractBinder() {
#Override
public void configure() {
bindAsContract(controllerClass);
}
};
locator = ServiceLocatorUtilities.bind(new TestBinder(), binder);
locator.inject(this);
}
#After
public void tearDown() {
if (locator != null) {
locator.shutdown();
}
}
}
Then in your concrete class
public class TestControllerTest extends AbstractControllerTest {
public TestControllerTest() {
super(TestController.class);
}
#Inject
private TestController controller;
#Test
public void doTest() {
assertNotNull(controller);
assertEquals("Hello Tests", controller.get());
}
}
If you spent some more time, I'm sure you could come up with a better abstract test class design. It was the first thing that came to mind for me.
Note: For anything request scoped, you mayb need to just mock it. When running the unit tests, there is no request context, so the test will fail.
See Also:
Using Jersey's Dependency Injection in a Standalone application
HK2 documentation
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
#BeforeClass
public static void doTest() {
ServiceLocator serviceLocator = ServiceLocatorUtilities.bind(new AbstractBinder() {
#Override
protected void configure() {
bindAsContract(YourClass1.class);
bindAsContract(YourClass2.class);
bindAsContract(YourClass3.class);
}
});
YourClass1 yourClass1 = serviceLocator.getService(YourClass1.class);
...
Related
In the following case, can I test "MainServiceImpl"?
【What I want to do】
・Test target object is "MainServiceImpl".
・SubMainServiceImpl is used without mock.
・SubSubMainServiceImpl.subSubSayHello method is used with mock.
You may say "double autowired class is not recommended to mock." I know, but I want to know the above test is technically possible.
//Test Target Object
#Service
public class MainServiceImpl implements MainService {
#Autowired
private SubMainService subMainService;
#Override
public String mainSayHello() {
return "MainSayHello. Also..." + subMainService.subSayHello();
}
}
//MainServiceImpl depends on this class.
#Service
public class SubMainServiceImpl implements SubMainService {
#Autowired
private SubSubMainService subSubMainService;
#Override
public String subSayHello() {
return "SubSayHello. Also..." + subSubMainService.subSubSayHello();
}
}
//SubSubServiceImpl depends on this class.
#Service
public class SubSubMainServiceImpl implements SubSubMainService {
#Override
public String subSubSayHello() {
return "SubSubSayHello";
}
}
As a side note, I can mock direct-autowired class.
How can I mock "double" autowired class?
//Direct(not double) autowired class is mocked.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MockMainServiceTest {
#InjectMocks
MainServiceImpl mainService;
#Mock
SubMainService subMainService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void SubMainService_Mocked(){
doReturn("SubMainService_Mocked").when(subMainService).subSayHello();
Assert.assertThat(mainService.mainSayHello() ,is("MainSayHello. Also...SubMainService_Mocked"));
}
}
Technology
SpringBoot 2.5.4
JUnit 4.2
Java 1.8
I want to use #Qualifier to dynamically specifying parameters? how to do it ?
#Qualifier("two") 'two' as a parameter ,can be 'one' 'three' or other.
Can i use aop dynamically design 'two'?
means I want to change the name of service with a #Qualifier by parameters.
the parameter from the url 'Token'.
case: url: http://localhost:8080/insert/order, token has a parameter: companyId = one
#RestController
public class ApiWebService {
#Autowired
#Qualifier("two")
//#Qualifier("one")
private BaseService baseService;
#GetMapping("insert/order")
public void test() {
baseService.insertOrder();
}
}
#Service("one")
public class CompanyOneService extends BaseService {
#Override
public void insertOrder() {
System.out.println("conpanyOne");
System.out.println("baseInsertOrder");
}
}
#Service("two")
public class CompanyTwoService extends BaseService {
#Override
public void insertOrder(){
System.out.println("companyTwo");
System.out.println("baseInsertOrder");
}
}
three
four
...
#Service
public class BaseService {
public void insertOrder(){
System.out.println("baseInsertOrder");
}
}
你好 !
No you cannot , mostly because the attribute in Java annotation does not allow to assign with variables.
Actually you want to choose an implementation to use based on some runtime conditions(i.e.companyId in your case). You can achieve it using factory pattern with #Configuration and #Bean which is much more elegant and easier to understand than your ugly AOP solution:
First define a factory:
#Configuration
public class ServiceFactory{
#Bean
public BaseService companyOneService(){
return new CompanyOneService();
}
#Bean
public BaseService companyTwoService(){
return new CompanyTwoService();
}
public BaseService getService(Integer companyId){
if(companyId == 1){
return companyOneService();
}else if(company==2){
return companyTwoService();
}else{
//blablablab
}
}
}
In the controller , inject the ServiceFactory to get the related Service based on the the company Id
#RestController
public class ApiWebService {
#Autowired
private ServiceFactory serviceFactory;
#GetMapping("insert/order")
public void test() {
Integer companyId = getCompanyIdFromToken(httpServletRequest);
BaseService service = serviceFactory.getService(companyId);
service.blablabla();
}
}
Inject (autowire) ApplicationContext into your class and use one of getBeans* method to find the exact bean you need.
aspect
#Aspect
#Component
public class ApiAspect {
#Pointcut("execution(* com.example.demo.control.ApiWebService.*(..))")
public void apiInputWebService() {
}
#Before("apiInputWebService()")
public void apiInputAuth(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes())
.getRequest();
String token = request.getHeader("Authorization");
//compangId can be from token
String compangId = "one";
Object target = joinPoint.getTarget();
Method method = target.getClass().getMethod("before", String.class);
method.invoke(target, compangId);
}
}
control
#RestController
public class ApiWebService {
private ApiService baseService;
#Autowired
private ApplicationContext applicationContext;
public void before(String company) {
baseService = (ApiService) applicationContext.getBean(company);
}
#GetMapping("insert/order")
public void test() {
baseService.insertOrder();
}
}
service
#Service
public class ApiService {
public void insertOrder(){
System.out.println("baseInsertOrder");
}
}
#Service("one")
public class CompanyOneService extends ApiService {
#Override
public void insertOrder() {
System.out.println("conpanyOne");
System.out.println("baseInsertOrder");
}
}
#Service("two")
public class CompanyTwoService extends ApiService {
#Override
public void insertOrder(){
System.out.println("companyTwo");
System.out.println("baseInsertOrder");
}
}
I am having some issues with a jukito unit test. I can't seem to mock Provider. Examples:.
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
#Singleton
public class ServiceClass1 {
#Inject
Provider<ConnectionClass> provider;
public void method1() {
Object o = provider.get().getO(); //during mainTest, provider is null and I get Nullpointer
}
}
#Singleton
public class ConnectionClass {
public Object getO() {
//this is not relevant
}
}
public class ConfigurationModule extends AbstractModule {
bind(ServiceClass1.class).in(Singleton.class);
bind(ConnectionClass.class).in(Singleton.class);
}
#RunWith(JukitoRunner.class)
public class ServiceClass1Test {
#InjectMocks
ServiceClass1 service;
#Mock
Provider<ConnectionClass> connectionClassProvider;
#Mock
ConnectionClass connectionClass;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(connectionClassProvider.get()).thenReturn(connectionClass);
}
#Test
public void mainTest() {
service.method1();
}
}
I expect to get a mocked connection class, but instead provider returns null. I tried binding connectionClass to TestSingleton, but that does not help. Inside test class:
public static class Module extends JukitoModule {
#Override
protected void configureTest() {
bindMock(ConnectionClass.class).in(TestSingleton.class);
}
}
If anyone has some suggestions for me, I would greatly appreciate it.
Using constructor injection instead of field injection:
#Singleton
public class ServiceClass1 {
Provider<ConnectionClass> provider;
#Inject
public ServiceClass1(Provider<ConnectionClass> provider) {
this.provider = provider;
}
}
and dropping InjectMocks:
#Mock
Provider<ConnectionClass> provider;
ServiceClass1 serviceClass1;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
serviceClass1 = new ServiceClass1(provider);
}
makes everything work. Can InjectMocks be the cause of this? Anyway, I guess this will do for now.
I want to inject MyService directly into my JerseyTest using CDI. Is it possible? MyService is succcefull injected into MyResource but I get NullPointerException when I try to access it from MyJerseyTest.
public class MyResourceTest extends JerseyTest {
#Inject
MyService myService;
private Weld weld;
#Override
protected Application configure() {
Properties props = System.getProperties();
props.setProperty("org.jboss.weld.se.archive.isolation", "false");
weld = new Weld();
weld.initialize();
return new ResourceConfig(MyResource.class);
}
#Override
public void tearDown() throws Exception {
weld.shutdown();
super.tearDown();
}
#Test
public void testGetPersonsCount() {
myService.doSomething(); // NullPointerException here
// ...
}
}
I think you need to provide an instance of org.junit.runner.Runner where you will do the weld initialization. This runner should also be responsible for providing an instance of your Test class with necessary dependencies injected. An example is shown below
public class WeldJUnit4Runner extends BlockJUnit4ClassRunner {
private final Class<?> clazz;
private final Weld weld;
private final WeldContainer container;
public WeldJUnit4Runner(final Class<Object> clazz) throws InitializationError {
super(clazz);
this.clazz = clazz;
// Do weld initialization here. You should remove your weld initialization code from your Test class.
this.weld = new Weld();
this.container = weld.initialize();
}
#Override
protected Object createTest() throws Exception {
return container.instance().select(clazz).get();
}
}
And your Test class should be annotated with #RunWith(WeldJUnit4Runner.class) as shown below.
#RunWith(WeldJUnit4Runner.class)
public class MyResourceTest extends JerseyTest {
#Inject
MyService myService;
// Test Methods follow
}
I have below class scenario. While testing MyTestableClass, I wish to process Autowired class.
I would like to mock only variable in AutoWired class.
sample class is as below-
public class MyTestableClass {
#Autowired
private MyServiceClass service;
public void handleError(){
...
service.doSomething();
}
}
public class MyServiceClass {
#Autowired
private JMSChannel channel;
public void doSomething(){
System.out.println("Inside Service class");
.....
channel.isAvailable();
.....
}
}
#RunWith(MockitoJUnitRunner.class)
public class MyTestableClassTest {
private MyTestableClass testClass= new MyTestableClass();
private JMSChannel channel;
#Before
public void init(){
channel= mock(JMSChannel.class);
when(channel.isAvailable()).thenReturn(Boolean.TRUE);
}
#Test
public void test(){
testClass.handleError();
}
}
For example, Console should give me "Inside Service class" before returning true.
Thanks in Advance !
You need to create and instance of your service (or a mock of it) and set its channel to your mocked one, then set MyTestableClass#service to this one. Something like:
#Before
public void init(){
channel= mock(JMSChannel.class);
when(channel.isAvailable()).thenReturn(Boolean.TRUE);
MyServiceClass service = new MyServiceClass();
ReflectionTestUtils.setField(service, "channel", channel);
myTestableClass = new MyTestableClass();
ReflectionTestUtils.setField(myTestableClass, "service", service);
}
with ReflectionTestUtils from spring-test (NB: you can use a setter instead)