I am trying to add some Unit Testing to some of our companies code. Yes, I know it should already be there, but not everyone seems to have the same view of unit testing that I do.
However, I have come against a bit of a stopper for me. Admittedly, my Java, Spring and Unit Testing knowledge are not all that they should be. My problem is this though:
I have added a unit test to my code, which tests a class. This class includes a bean which has scope="request", and when it tries to instantiate the bean it throws an exception:
java.lang.IllegalStateException: No Scope registered for scope 'request'
I believe this is because I don't have a HttpServletRequest object, but I don't know how to create a mock one of these and also I don't know how, once created, to add this Mock Object to the unit test so that it resolves this problem.
Below is a cut down version of the code involved, which I believe includes all of the details that are part of this problem.
How can I get this to work?
#Test
public void handleRequest() {
try {
Message<?> outMessage = (Message<?>) response.handleRequest(map);
} catch (Exception e) {
assertNotNull(e);
}
outMessage.getPayload().toString());
}
public class upddResponse extends AbstractResponseTransform {
#SuppressWarnings("unchecked")
public Message<?> handleRequest(Map<String, Message<?>> messages) throws Exception {
super.addEnvironmentDetails(serviceResponseDocument.getServiceResponse());
}
public abstract class AbstractResponseTransform implements ResponseTransform,
ApplicationContextAware {
private ApplicationContext applicationContext;
private MCSResponseAggregator mcsResponseAggregator;
public ServiceResponseType addEnvironmentDetails(ServiceResponseType serviceResponse) throws Exception {
try {
mcsResponseAggregator = (MCSResponseAggregator) applicationContext
.getBean("mcsResponseAggregator");
}
catch (Exception ex) {
}
}
}
public interface ResponseTransform extends Transform {
public Message<?> handleRequest(Map<String, Message<?>> messages)
throws Exception;
}
<bean id="mcsResponseAggregator" class="com.company.aggregator.MCSResponseAggregator" scope="request" />
You need a WebApplicationContext to handle beans with: scope="request"
I recommend to use stub objects with Spring integration tests and use EasyMock without Spring when you test a class isolated.
You can use mocks within the Spring Context:
but that will not solve your problem as it will not make Spring understand scope="request". You can create your own implementation of the request scope, but I'm getting the feeling that you're better off not going through all this trouble.
The easy way out would be to override your request scoped bean in a little test context. You're technically not testing the original context then, but you will be done a lot quicker.
Spring 3.2 comes with support for this. See "Spring MVC Test Framework"
Related
Quarkus 2.8.0.Final introduced QuarkusTransaction. What is the difference between
#ApplicationScoped
public class MyClass {
#Inject
TransactionManager tm;
public void doSomething() throws Exception {
tm.begin();
// ...
tm.commit();
}
}
and
#ApplicationScoped
public class MyClass {
public void doSomething() {
QuarkusTransaction.begin();
// ...
QuarkusTransaction.commit();
}
}
?
I am using the TransactionManager in a lot of my tests, and when I replaced it with QuarkusTransaction, I am getting different error messages when something fails:
When using the TransactionManager, I am getting
javax.transaction.NotSupportedException: BaseTransaction.checkTransactionState - ARJUNA016051: thread is already associated with a transaction!
When using QuarkusTransaction, I am getting
javax.enterprise.context.ContextNotActiveException
The Quarkus documentation does not really explain why QuarkusTransaction was introcuded 🤔
QuarkusTransaction was introduced with this Pull Request and the idea is to provide an easier to use Transactions API.
As can be seen in this test, it's meant to be used when a request scope is active
I have a Hibernate Search ClassBridge where I want to use #Inject to inject a Spring 4.1 managed DAO/Service class. I have annotated the ClassBridge with #Configurable. I noticed that Spring 4.2 adds some additional lifecycle methods that might do the trick, but I'm on Spring 4.1
The goal of this is to store a custom field into the index document based on a query result.
However, since the DAO, depends on the SessionFactory getting initialized, it doesn't get injected because it doesn't exist yet when the #Configurable bean gets processed.
Any suggestions on how to achieve this?
You might try to create a custom field bridge provider, which could get hold of the Spring application context through some static method. When provideFieldBridge() is called you may return a Spring-ified instance of that from the application context, assuming the timing is better and the DAO bean is available by then.
Not sure whether it'd fly, but it may be worth trying.
Hibernate Search 5.8.0 includes support for bean injection. You can see the issue https://hibernate.atlassian.net/browse/HSEARCH-1316.
However I couldn't make it work in my application and I had implemented a workaround.
I have created an application context provider to obtain the Spring application context.
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
ApplicationContextProvider.context = context;
}
}
I have added it to the configuration class.
#Configuration
public class RootConfig {
#Bean
public ApplicationContextProvider applicationContextProvider() {
return new ApplicationContextProvider();
}
}
Finally I have used it in a bridge to retrieve the spring beans.
public class AttachmentTikaBridge extends TikaBridge {
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
// get service bean from the application context provider (to be replaced when HS bridges support beans injection)
ApplicationContext applicationContext = ApplicationContextProvider.getApplicationContext();
ExampleService exampleService = applicationContext.getBean(ExampleService .class);
// use exampleService ...
super.set(name, content, document, luceneOptions);
}
}
I think this workaround it's quite simple in comparision with other solutions and it doesn't have any big side effect except the bean injection happens in runtime.
I would like to be able to test a route which consumes from a queue then does some work in a bean involving a spring injected service and use mockito to effectively mock out this service.
My spring route is as follows:
<camel:route id="msgemailqueue-to-emailservice">
<camel:from uri="activemq:emails" />
<camel:bean ref="emailService" method="createEmailRequest"/>
</camel:route>
The emailService bean has an autowired service which is then called in the createEmailRequest() which goes off to another service and retrieves user data to be used subsequently.
The test:
#RunWith(MockitoJUnitRunner.class)
public class TroubledEmailServiceImplTest extends CamelSpringTestSupport {
#Produce(context = "messagingCamelContext")
protected ProducerTemplate producer;
#Mock
private UserRestService userRestService;
#Override
protected AbstractApplicationContext createApplicationContext() {
return new ClassPathXmlApplicationContext("messaging-camel-route-test-context.xml");
}
#Test
public void testUserResponseToEmailQueue() throws Exception {
context.addRoutes(new MyDynamcRouteBuilder(context, "direct:addEmailRequest", "activemq:emails"));
Mockito.when(userRestService.getUserById(Mockito.anyLong())).thenReturn(
new WebServiceResult<UserVO>(new UserVO()));
CreateMessageRequest msgReq = new CreateMessageRequest();
producer.sendBody("direct:addEmailRequest", msgReq);
Mockito.verify(userRestService).getUserById(Mockito.anyLong());
assertMockEndpointsSatisfied();
}
The bean as follows:
#Override
public void createEmailRequest(final CreateMessageRequest request) throws CreateEmailException {
LOGGER.trace("Entering createEmailRequest(request) " + Arrays.asList(new Object[] { request }));
Validate.notNull(request, "CreateMessageRequest was null");
WebServiceResult<UserVO> response;
try {
response = userRestService.getUserById(request.getId());
} catch (final WebServiceException e) {
throw new CreateEmailException("Error lookup up user data for email", e);
}
final UserVO userResponse = response.getData();
All compiles ok and when running the route fires as an object is popped on the queue which is then passed to the bean and the createEmailRequest is invoked and the call to the mockito mocked service happens ok
response = userRestService.getUserById(request.getId());
but the response is null even though
Mockito.when(userRestService.getUserById(Mockito.anyLong())).thenReturn(
new WebServiceResult<UserVO>(new UserVO()));
was performed in the test. It appears that the service in bean is a different instance i.e. mockito mock is never invoked.
I am doing something wrong and perhaps my testing approach is all wrong as well but should this work in theory? I'd really like to be able to mock out a service in a bean in my camel route.
I'm using Camel Enhanced Spring Test and have passed through the same issue. I only changed #Mock to #MockBean. My Camel version is 2.18.
the mock userRestService you create in the test has to be the same instance you use in the bean. I do not see where you are setting the userRestService for the createEmailRequest method. That service needs to be the same mock object as you create in your test.
I have resolved this - mea culpa. My test class was effectively creating two instances of the service - one through the spring application context and another due to the #RunWith(MockitoJUnitRunner.class) plus #mock annotation. Now resolved by doing the mock creation once. To sum up this was a spring wiring issue only on my part. Many thanks #mike-pone.
Here's a weird one. I've got a few tests failing because an aspect is being applied, so an autowired service is null, bad things ensue. The issue is that I can't understand how the aspect is even being applied, since in the test I construct the object under test with new.
#RunWith(MockitoJUnitRunner.class)
public class TheControllerTest {
#Spy
private TheController controller = new TheController();
#Mock
private HttpServletRequest request;
#Mock
private ConfigService configService;
....
#Before
public void setup() {
controller.setConfigService(configService);
....
}
#Test
public void testGetAccountsList() throws Exception {
Mockito.when(accountService.getAllAccounts()).thenReturn(Arrays.asList(account1, account2));
Map<String, Object> result = controller.getAccountsList(request);
...
}
}
I'm obviously omitting plenty of code, but really, I just don't understand how, given how controller is instantiated, it could have had the advice applied.
One possible reason could be if you are running this in Eclipse - in a project with ajbuilder enabled, even if you are explicitly expecting Spring AOP through dynamic proxies, ajbuilder would actually perform compile team weaving, and hence you would see advice enhanced classes even using normal "new". Can you please check this, the fix would be to disable "ajbuilder" - here is one reference - JUnit weaving wrong Spring AOP Bean
I am fairly new to Sprint and am using Spring 3.x and roo1.1.1 for my application.
I have multiple implementation of an interface which would be #Autowired into other different classes. I would only be able to decide which implementation to go with at the runtime. This should be achieved with like a factory pattern.
public interface SomeInterface {
public void doSomething();
}
Implementation 1.
public class SomeOb implements SomeInterface {
public void doSomething() {
//Do something for first implementation here
}
}
Implementation 2.
public class SomeOtherOb implements SomeInterface {
public void doSomething() {
//Do something for first implementation here
}
}
Now in my service i needed this Autowired like
#Service
public class MyService {
#Autowired
SomeInterface ob;
//Rest of the code here
}
1) The logic to choose which implementation to be Autowired is only know runtime, so i cannot use the #Qualifier annotation to qualify this.
2) I tried to create a FactoryBean like
public class SomeFactoryBean implements FactoryBean<SomeInterface> {
#Override
public SomeInterface getObject() throws Exception {
if(/*Somecondition*/) {
return new SomeOb();
} else
return new SomeOtherOb();
}
#Override
public Class<? extends SomeInterface> getObjectType() {
if(/*Somecondition*/) {
return SomeOb.class;
} else
return SomeOtherOb.class;
}
#Override
public boolean isSingleton() {
return false;
}
}
In the applicationContext.xml i have the tag mentioned.
When i run the webserver i run into an error like
No unique bean of type [com.xxxx.xxxx.SomeInterface] is defined: expected single matching bean but found 3: [xxxx, xxxxxxx, xxxxFactory]
Can anyone please help me to resolve this issue. If i am not doing this right please direct me to do this the right way.
Thanks and appreciate any help,
jjk
Thanks for the suggestion. I was able to solve the problem with help from a colleague. What i was doing wrong
I had the implementation of the SomeInterface with #Service. So this was picked up by the spring scanner and added to the bean.
During trial and error i removed the #Component annotation from by FactoryBean implementation.
After making these changes it worked like a charm.
return true from isSingleton() if you only need one implementation of the bean for a given instance of your application
But I question your design.
I would always use properties files to switch out implementations like this. I once had to implement CAPTCHA integration for a site. We were prototyping with the JCaptcah and ReCAPTCHA APIs. I created a new interface that contained just the functionality we needed and then created implementations for both APIs. Using a placeholders in the Spring configuration file and Maven profiles, we could switch out the implementation class at compile time or deployment time, for example, mvn jetty:run -DcaptchaImpl=recaptcha or -DcaptchaImpl=jcaptcha.
Without knowing the task that you want to accomplish, it's hard to provide more advice.