I am writing acceptance tests (testing the behavior) using cucumber-jvm, on an application with Struts 2 and Tomcat as my Servlet Container. At some point in my code, I need to fetch the user from the Struts 2 HttpSession, created by an HttpServletRequest.
Since I'm doing tests and not running Tomcat, I don't have an active session and I get a NullPointerException.
Here's the code I need to call:
public final static getActiveUser() {
return (User) getSession().getAttribute("ACTIVE_USER");
}
And the getSession method:
public final static HttpSession getSession() {
final HttpServletRequest request (HttpServletRequest)ActionContext.
getContext().get(StrutsStatics.HTTP_REQUEST);
return request.getSession();
}
In all honesty, I don't know much about Struts 2, so I need a little help. I've been looking at this cucumber-jvm with embedded tomcat example, but I'm struggling to understand.
I've also been looking at this Struts 2 Junit Tutorial. Sadly, it doesn't cover very well all the StrutsTestCase features and it's the simplest of use cases (all considered, a pretty useless tutorial).
So, how can I run my acceptance test as if the user was using the application?
UPDATE:
Thanks to Steven Benitez for the answer!
I had to do two things:
Mock the HttpServletRequest, as suggested,
Mock the HttpSession to get the attribute I wanted.
here's the code I've added to my cucumber-jvm tests:
public class StepDefs {
User user;
HttpServletRequest request;
HttpSession session;
#Before
public void prepareTests() {
// create a user
// mock the session using mockito
session = Mockito.mock(HttpSession.class);
Mockito.when(session.getAttribute("ACTIVE_USER").thenReturn(user);
// mock the HttpServletRequest
request = Mockito.mock(HttpServletRequest);
Mockito.when(request.getSession()).thenReturn(session);
// set the context
Map<String, Object> contextMap = new HashMap<String, Object>();
contextMap.put(StrutsStatics.HTTP_REQUEST, request);
ActionContext.setContext(new ActionContext(contextMap));
}
#After
public void destroyTests() {
user = null;
request = null;
session = null;
ActionContext.setContext(null);
}
}
An ActionContext is a per-request object that represents the context in which an action executes. The static methods getContext() and setContext(ActionContext context) are backed by a ThreadLocal. In this case, you can call this before your test:
Map<String, Object> contextMap = new HashMap<String, Object>();
contextMap.put(StrutsStatics.HTTP_REQUEST, yourMockHttpServletRequest);
ActionContext.setContext(new ActionContext(contextMap));
And then clean it up after with:
ActionContext.setContext(null);
This example will only provide what the method you are testing needs. If you need additional entries in the map based on code you didn't provide here, then just add them accordingly.
Hope that helps.
Related
I have to invoke an external api in multiple places within my Springboot java application. The external api will just return a static constant String value at all times.
Please find below sample code to explain better my intentions and what I wish to achieve at the end of the day
My sample code invoking external api using RestTemplate to retrieve a String value.
ResponseEntity<String> result = new RestTemplate().exchange("http://localhost:7070/api/test/{id}",
HttpMethod.GET, entity, String.class, id);
JSONObject jsonResponse = new JSONObject(result.getBody());
String reqVal = jsonResponse.getString("reqKey");
Now, My intention is to make this String globally available within the application to avoid calling this api multiple times.
I am thinking of invoking this extenal api at application startup and set this String value in Springboot application context, so that it can be retrieved from anywhere with the application.
Can anyone suggest, how can I achieve my above requirement?
Or are there any other better options to think of?
Thanks in advance!
I would store it in memory in the Spring-managed Bean that calls the external API and then allow any other Spring-managed Bean to get it from this component.
#Service
public class ThirdPartyServiceClient implements ApplicationListener<ContextRefreshedEvent> {
private String reqKey = null;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
(...)
ResponseEntity<String> result = new RestTemplate()
.exchange("http://localhost:7070/api/test/{id}", HttpMethod.GET, entity, String.class, id);
JSONObject jsonResponse = new JSONObject(result.getBody());
this.reqKey = jsonResponse.getString("reqKey");
}
public String getKey() {
return reqKey;
}
}
Now you just need to inject ThirdPartyServiceClient Spring-managed bean in any other to be able to call getKey() method.
I have been trying to write a test case for the following line of code but I keep getting java.lang.NullPointerException, I have tried to follow/replicate what others have suggested here Unit testing with Spring Security but I have had no luck. Can someone please help me better identify or give me a hint what I need to do. (I'm using mockito for this)
Code:
if (SecurityContextHolder.getContext().getAuthentication().getPrincipal().equals(user)) {
continue;
}
Test case:
#Test
public void testExpireAllSession() throws Exception {
SecurityContext securityContext = Mockito.mock(SecurityContext.class);
Mockito.when(securityContext.getAuthentication().getPrincipal().equals(any(Object.class))).thenReturn(false);
SecurityContextHolder.setContext(securityContext);
controller.theMEthodUnderTest();
}
..
There are 2 problems with your test :
You must mock each "level" of method calls, you should mock :
SecurityContext.getAuthentication()
Authentication.getPrincipal()
Principal.equals()
But, you can't mock .equals(), see Mockito FAQ - limitations and Mockito - Issue 61.
You have to design your code/test differently. For example, pass a 'user' principal to your method arguments, and make Authentication.getPrincipal() return another one (they will be different, thus making the equals return false) :
Code
public void theMethod(Principal user) {
...
if (SecurityContextHolder.getContext().getAuthentication().getPrincipal().equals(user)) {
continue;
}
...
}
Test
#Test public void testController() {
SecurityContext securityContext = Mockito.mock(SecurityContext.class);
Authentication authentication = Mockito.mock(Authentication.class);
Principal principal1 = Mockito.mock(Principal.class);
Principal principal2 = Mockito.mock(Principal.class);
Mockito.when(authentication.getPrincipal()).thenReturn(principal1);
Mockito.when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
new Controller().theMethod(principal2);
}
Few important things I did to make this work and hope this helps others as well.
Used the #InjectMocks :
#InjectMocks
private static YourMainController controller;
Mocked the dependencies which would get added to the main mock above:
#Mock
SecurityContext securityContextMocked;
#Mock
Authentication authenticationMocked;
#Mock
Principal principal1;
Modified the test to look like this and that made it work nicely.
#Test
public void testExpireAllSession() throws Exception {
List mySessions = new ArrayList<>();
Object principal="";
Date aDate = new Date();
SessionInformation sessionInformation = new SessionInformation(principal,”100000”,aDate);
mySessions.add(sessionInformation);
allUsers.add("Mike");
when(authenticationMocked.getPrincipal()).thenReturn(principal1);
when(securityContextMocked.getAuthentication()).thenReturn(authenticationMocked);
SecurityContextHolder.setContext(securityContextMocked);
when(sessionRegistryMocked.getAllSessions(allUsers,false)).thenReturn(sessions);
when(sessionRegistryMocked.getAllPrincipals()).thenReturn(allUsers);
controller.expireAllSession();
verify(sessionRegistryMocked).getAllPrincipals();
}
I'm trying to set up unit testing. I'm using Struts2 and Liferay 6.1.
I'm getting the below error
java.lang.NullPointerException
at com.liferay.portal.util.PortalUtil.getCompany(PortalUtil.java:305)
at com.mycomp.portlet.action.BasePortletAction.setupSiteAgent(BasePortletAction.java:1169)
This is because PortalUtil.getPortal() returns null. Is there a way I could somehow create a mock portal? There is no MockPortal class. I have found something called MockPortalContext but I'm not sure how to make use of it.
This is my code so far
BaseTestCase.java
public abstract class BaseTestCase extends TestCase {
private Dispatcher dispatcher;
protected ActionProxy proxy;
private static MockServletContext servletContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
public BaseTestCase(String name) {
super(name);
}
#SuppressWarnings("unchecked")
protected <T>T createAction(Class<T> theClass, String namespace, String actionName, String methodName, HashMap<String, Object> actionContextMap, HashMap<String, Object> parameterMap) throws Exception {
proxy = dispatcher.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, actionName, methodName, new HashMap<String, Object>(), true, true);
for (String key : actionContextMap.keySet()) {
proxy.getInvocation().getInvocationContext().put(key, actionContextMap.get(key));
}
proxy.getInvocation().getInvocationContext().setParameters(parameterMap);
proxy.setExecuteResult(true);
ServletActionContext.setContext(proxy.getInvocation().getInvocationContext());
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
ServletActionContext.setRequest(request);
ServletActionContext.setResponse(response);
ServletActionContext.setServletContext(servletContext);
return (T) proxy.getAction();
}
protected void setUp() throws Exception {
final String[] config = new String[]{"struts.xml", "mockApplicationContext.xml"};
servletContext = new MockServletContext();
final XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setServletContext(servletContext);
appContext.setConfigLocations(config);
appContext.refresh();
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, appContext);
HashMap<String, String> params = new HashMap<String, String>();
params.put("actionPackages", "com.mycomp.portlet.action");
dispatcher = new Dispatcher(servletContext, params);
dispatcher.init();
Dispatcher.setInstance(dispatcher);
}
}
ActionTest.java
public class ActionTest extends BaseTestCase {
private Map<String, Object> contextMap;
private Map<String, Object> parameterMap;
private MockPortletRequest portletRequest;
private final String REQUEST_LOCALE = "request_locale";
public ActionTest(String name) {
super(name);
}
public void testShowDetail() throws Exception {
init();
parameterMap = new HashMap<String, Object>();
parameterMap.put(REQUEST_LOCALE, "en_GB");
#SuppressWarnings("unused")
PortletAction lspa = createAction(PortletAction.class,
"/view",
"myAction",
"myAction",
(HashMap<String, Object>)contextMap,
(HashMap<String, Object>)parameterMap);
String result = proxy.execute();
System.out.println(result);
}
private void init() {
portletRequest = new MockPortletRequest();
contextMap = new HashMap<String, Object>();
contextMap.put(PortletActionConstants.REQUEST, portletRequest);
}
}
The Spring documentation says creating a MockPortletRequst() with the no-arg constructor creates it with a default MockPortletContext and MockPortalContext so I don't know why it's null.
Use Powermock or jMockit to mock the static method call PortalUtil.getPortal()
Technically the answer has already been given by John B. I'd like to add a philosophical angle.
IMHO mocking a complex environment like a portal doesn't buy a lot, especially when we speak about unit testing. You'll gain more insight into your code by minimizing contact with any complex API and environment (not just a portal), rather decouple from that APIs.
One solution is to have very simple wiring in portlet classes (and code-review this) while extracting testable code into its own - fully tested - classes that don't call out to the external API, rather get their context passed in.
This would leave you with some very simple code that's not unit-tested, but in addition to the code-review you can (and should) add some integration/smoke tests that actually work in the full environment.
Sometimes a simple solution will be to quickly mock portal classes (or other external classes), but I don't see this as the preferred solution. As soon as you start writing significant setup code to prepare the environment, you've gained nothing when your test runs. And if it fails, you'll have to check it in the real environment anyways to see if your setup was accurate.
Sorry if this is bad news - IMHO it's inherent when you have any given API that has not been built with being replaceable in unit tests. And with my unwillingness to routinely have large setup routines in unit tests. I'd not call them unit tests, if this happens frequently - rather break down the (too complex) unit into more smaller ones. Or accept code-review for simple wiring (adaptation) code between two different APIs.
I need to pass in a non-null HttpServletRequest somehow or control HelperClass.getUser(request) which is throwing an error(the method does request.getHeader() inside and as the request is null it throws an error and fails the test).
We would like to unit test our REST API, so we quickly know if a change (or a developer) breaks it or if we have a bug etc. eventually to start doing TDD/BDD. These tests are destined eventually for use for automated builds by ant. I can call REST API and set headers from java but don't think this would be a unit test? Would depend on running it on local host? Could be wrong about that
Code:
#Path("service/")
public class Service {
static DataAccess da = null;
#javax.ws.rs.core.Context HttpServletRequest request;
//constructor where doa will be mock object
public Service(DataAccess dao){
da = dao;
}
#GET
#Path("custom-object/{date}/{batch}")
#Produces({"application/xml", "application/json", "application/x-protobuf"})
//Method to test
public CustomObject getCustomObject(
#PathParam("date") String date,
#PathParam("batch") int batch,
#QueryParam("type") String type) {
String user = HelperClass.getUser(request); // this is causing me issues
//da will be a mock object
CustomObject returnedVal = da(user,DatatypeConverter.parseDateTime(date).getTime(), batch, artifactType);
return returnedVal;
}
}
Test using junit/mockito (happy to use powermock as a solution) :
#Test
public void testGetCustomObject() {
System.out.println("getCustomObject");
//Arrange
DataAccess da = mock(DataAccess.class);
Service instance = new Service(da);
String date = "2010-04-05T17:16:00Z";
int batch = 0;
String type = "";
CustomObject expResult = null;
//Act
CustomObject result = instance.getCustomObject(date, batch, type);
//Assert
assertEquals(expResult, result);
}
The test passes if I hardcode the String user="123";. I need to get over this problem before writing useful tests. Can anyone give example code to mock/control this line of code to effectively set user as a non-null string (this line String user = HelperClass.getUser(request); is in EVERY API method)
You could try the REST assured library to help unit test REST services.
For constructing HttpServletRequest you could use some mock object like Spring's MockHttpServletRequest.
Or you could refactor your helper class and make the getUser() method non-static and use Mockito to mock it in your unit tests.
I am using the Struts2 framework and have the following method in a POJO class.
public String execute() {
setUserPrincipal();
//do something
someMethod(getUserPrincipal().getLoggedInUserId());
return SUCCESS;
}
the setUserPrincipal() method looks like this
public void setUserPrincipal() {
this.principal = (UserPrincipal) getServletRequest().getSession().getAttribute("principal");
}
Basically it is simply taking a session attribute named "principal" and setting it so that I can find out who the logged in user is. The call to setUserPrincipal() to do this is quite common in most of my POJOs and it also becomes a hassle when testing the method because I have to set a session attribute.
Is there a way to automatically inject the session attribute into the POJO either using Spring or something else?
I've only used Struts2 a bit, but they have an interceptor stack that you can tie to particular actions. You can create your own interceptor that injects the session variable.
public interface UserAware
{
void setUserPrincipal(String principal);
}
// Make your actions implement UserAware
public class MyInterceptor implements Interceptor
{
public String intercept(ActionInvocation inv) throws Exception
{
UserAware action = (UserAware) inv.getAction();
String principal = inv.getInvocationContext().getSession().get("principal");
action.setUserPrincipal(principal);
return inv.invoke();
}
}
Like I said, not much Struts2 experience so this is untested but I think the idea is there.
Don't know about injecting the session, but maybe having a piece of AOP code that sets principal before execute.
Here's some documentation:
http://static.springsource.org/spring/docs/2.5.x/reference/aop.html