I do want to write some functional tests for our project. Techstack: Play Framework 1.5, Java 16, Junit 3.
I found following documentation:
test - 1.5.x
security - 1.5.x
So the Controller looks something like this.
#AllowFeature(Feature.SEARCH_BOX)
public static void search(String term) {
//implementation omitted
TablePage<MyAwesomeType> page = search(term);
render(..., page, term);
}
And my test looks like this
public class SearchTest extends FunctionalTest {
#Test
public void search_withResults() {
String term = "ABC";
Http.Response response = GET("/foo/search?term=" + term);
assertStatus(302, response);
assertThat(renderArgs("page"), is(notNullValue()));
TablePage<MyAwesomeType> page = (TablePage<MyAwesomeType>) renderArgs("page");
assertTrue(page.getTotalRecords() >= 1);
}
}
However, the TablePage<MyAwesomeType> page is null when it really should not be, and i am unable to step into the controller method with the debugger. So it looks like the controller method search(...) is not called at all.
The response Code is 302 - Found but I think this might be play suggestion it found the path /foo/search
My guess is that i need to setup some UserContext or send a authenticityToken along with the request. So play can check the required feature #AllowFeature(Feature.A_SEARCH_BOX).
Does anybody know how I would setup such a functional test?
Any help is appreciated. Thanks!
I was able to figure this out.
I need to log into the application and then the play FunctionalTest.class takes care of the cookie.
Add #NoAuthenticityto the login method
#NoAuthenticity // <-- This enables execution without authenticityToken
public static void login(#Required String username, #Required String password) {
...
}
Post a request to login before the test.
#Test
public void search_withResults() {
// 1. login
Map<String, String> credentials = Map.of("username", "MyUsername", "password", "MyPassword");
POST("/login", credentials);
// Note: session info / authenticityToken is stored in a cookie
// FunctionalTest.class makes sure to use this cookie for subsequent requests
// This request now works like a charm
String term = "ABC";
Http.Response response = GET("/foo/search?term=" + term);
assertStatus(302, response);
assertThat(renderArgs("page"), is(notNullValue()));
TablePage<MyAwesomeType> page = (TablePage<MyAwesomeType>) renderArgs("page");
assertTrue(page.getTotalRecords() >= 1);
}
Note: One can use the JUnit #Before Annotation to simplify the test class.
#Before
public void login(){
Map<String, String> credentials = Map.of("username", "MyUsername", "password", "MyPassword");
POST("/login", credentials);
}
#Test
public void search_withResults() {
String term = "ABC";
Http.Response response = GET("/foo/search?term=" + term);
assertStatus(302, response);
assertThat(renderArgs("page"), is(notNullValue()));
TablePage<MyAwesomeType> page = (TablePage<MyAwesomeType>) renderArgs("page");
assertTrue(page.getTotalRecords() >= 1);
}
#Test
public void anotherTest() { ... }
#Test
public void yetAnotherTest() { ... }
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I have this method which I want to test using mockito
public boolean verifyState(HttpServletRequest request) {
String stateToken = getCookieByName(request, STATE_TOKEN);
String authToken = getCookieByName(request, AUTHN);
boolean isValidState = !stateToken.isEmpty() && !authToken.isEmpty();
if (isValidState) {
return true;
}
else {
return false;
}
}
It does two calls to getCookieName(), which has this implementation.
public String getCookieByName(HttpServletRequest request, String cookieName) {
try {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(cookieName)) {
return cookie.getValue();
}
}
}
} catch (Exception e) {
ExceptionLogger.logDetailedError("CookieSessionUtils.getCookieByName", e);
log.error("Error on Cookie " + e.getMessage());
}
return "";
}
I then have this for my tests:
#WebMvcTest(value = CookieSessionUtils.class, includeFilters =
{#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {ApiOriginFilter.class})})
class CookieSessionUtilsTest {
#Autowired
private CookieSessionUtils cookieSessionUtils;
#Mock
private HttpServletRequest request;
#BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testVerifyState() {
Cookie mockCookie1 = Mockito.mock(Cookie.class);
Cookie mockCookie2 = Mockito.mock(Cookie.class);
when(mockCookie1.getName()).thenReturn("stateToken");
when(mockCookie1.getValue()).thenReturn("stateToken");
when(mockCookie2.getName()).thenReturn("authn");
when(mockCookie2.getValue()).thenReturn("authn");
when(request.getCookies()).thenReturn(new Cookie[]{mockCookie1, mockCookie2});
when(cookieSessionUtils.getCookieByName(request, "stateToken")).thenReturn("stateToken");
when(cookieSessionUtils.getCookieByName(request, "authn")).thenReturn("authn");
assertTrue(cookieSessionUtils.verifyState(request));
}
However, it's always failing returning false falling into the return "" for the getCookieByName() method which seems to be triggered multiple times and having the value for getName() and getValue() overwritten by the other cookie, so it fails on (cookie.getName().equals(cookieName)). Not sure what I'm doing wrong.
Thank you.
No need to be so complicated. For mocking the servlet stuff, you can simply use the mock implementation provided by spring-test. It is more convenient to use than Mockito.
You can simply write your test case as :
#Test
public void testVerifyState() {
MockHttpServletRequest request = MockMvcRequestBuilders.get("/dummy")
.cookie(new MockCookie("stateToken", "stateToken"))
.cookie(new MockCookie("authn", "authn"))
.buildRequest(new MockServletContext());
assertTrue(cookieSessionUtils.verifyState(request));
}
Also , if the CookieSessionUtils that you are testing is just an utility class which does not have other spring bean dependencies, you can further simplify your test to just a plain JUnit test rather than a #WebMvcTest.
Based on #kenChan's answer, both of the below methods work. The issue was actually something very subtle and trivial. The value for the cookie was "authn" but its name is "Authn", so there wasn't a match between them due to capitalization.
private static final String STATE_TOKEN = "stateToken";
private static final String AUTHN = "Authn";
#Test
public void testVerifyState1() {
Cookie mockCookie1 = Mockito.mock(Cookie.class);
Cookie mockCookie2 = Mockito.mock(Cookie.class);
when(mockCookie1.getName()).thenReturn("stateToken");
when(mockCookie1.getValue()).thenReturn("stateToken");
when(mockCookie2.getName()).thenReturn("authn");
when(mockCookie2.getValue()).thenReturn("Authn");
when(request.getCookies()).thenReturn(new Cookie[]{mockCookie1, mockCookie2});
when(cookieSessionUtils.getCookieByName(request, "stateToken")).thenReturn("stateToken");
when(cookieSessionUtils.getCookieByName(request, "Authn")).thenReturn("Authn");
assertTrue(cookieSessionUtils.verifyState(request, ""));
}
#Test
public void testVerifyState() {
MockHttpServletRequest request = MockMvcRequestBuilders.get("/dummy")
.cookie(new MockCookie("stateToken", "stateToken"))
.cookie(new MockCookie("Authn", "authn"))
.buildRequest(new MockServletContext());
assertTrue(cookieSessionUtils.verifyState(request, ""));
}
I'm attempting to use Retrofit to call the GitHub API to update the contents of an existing file, but am getting 404s in my responses. For this question, I'm interested in updating this file. Here is the main code I wrote to try and achieve this:
GitHubUpdateFileRequest
public class GitHubUpdateFileRequest {
public String message = "Some commit message";
public String content = "Hello World!!";
public String sha = "shaRetrievedFromSuccessfulGETOperation";
public final Committer committer = new Committer();
private class Committer {
Author author = new Author();
private class Author {
final String name = "blakewilliams1";
final String email = "blake#blakewilliams.org";
}
}
}
**GitHubUpdateFileResponse **
public class GitHubUpdateFileResponse {
public GitHubUpdateFileResponse() {}
}
GitHubClient
public interface GitHubClient {
// Docs: https://docs.github.com/en/rest/reference/repos#get-repository-content
// WORKS FINE
#GET("/repos/blakewilliams1/blakewilliams1.github.io/contents/qr_config.json")
Call<GitHubFile> getConfigFile();
// https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents
// DOES NOT WORK
#PUT("/repos/blakewilliams1/blakewilliams1.github.io/contents/qr_config.json")
Call<GitHubUpdateFileResponse> updateConfigFile(#Body GitHubUpdateFileRequest request);
}
Main Logic
// Set up the Retrofit client and add an authorization interceptor
UserAuthInterceptor interceptor =
new UserAuthInterceptor("blake#blakewilliams.org", "myActualGitHubPassword");
OkHttpClient.Builder httpClient =
new OkHttpClient.Builder().addInterceptor(interceptor);
Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.client(httpClient.build()).build();
client = retrofit.create(GitHubClient.class);
// Now make the request and process the response
GitHubUpdateFileRequest request = new GitHubUpdateFileRequest();
client.updateConfigFile(request).enqueue(new Callback<GitHubUpdateFileResponse>() {
#Override
public void onResponse(Call<GitHubUpdateFileResponse> call, Response<GitHubUpdateFileResponse> response) {
int responseCode = response.code();
// More code on successful update
}
#Override
public void onFailure(Call<GitHubUpdateFileResponse> call, Throwable t) {
Log.e("MainActivity", "Unable to update file" + t.getLocalizedMessage());
}
});
What currently happens:
Currently, the success callback is triggered, but with a response code of 404 like so:
Response{protocol=http/1.1, code=404, message=Not Found, url=https://api.github.com/repos/blakewilliams1/blakewilliams1.github.io/contents/qr_config.json}
Has anyone else encountered this? I first thought it was a problem with including '/content/' in the URL but I do the same thing for reading the file contents request and it works fine (also uses same URL just a GET instead of PUT).
For anyone interested in doing this in the future, I figured out the solution.
I needed to revise the request object structure
Rather than using an authentication interceptor, I instead added an access token to the header. Here is where you can create access tokens for Github, you only need to grant it permissions to the 'repos' options for this use case to work.
This is what my updated request object looks like:
public class GitHubUpdateFileRequest {
public String message;
public String content;
public String sha;
public final Committer committer = new Committer();
public GitHubUpdateFileRequest(String unencodedContent, String message, String sha) {
this.message = message;
this.content = Base64.getEncoder().encodeToString(unencodedContent.getBytes());
this.sha = sha;
}
private static class Committer {
final String name = "yourGithubUsername";
final String email = "email#yourEmailAddressForTheUsername.com";
}
}
Then from my code, I would just say:
GitHubUpdateFileRequest updateRequest = new GitHubUpdateFileRequest("Hello World File Contents", "This is the title of the commit", shaOfExistingFile);
For using this reqest, I updated the Retrofit client implementation like so:
// https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents
#Headers({"Content-Type: application/vnd.github.v3+json"})
#PUT("/repos/yourUserName/yourRepository/subfolder/path/to/specific/file/theFile.txt")
Call<GitHubUpdateFileResponse> updateConfigFile(
#Header("Authorization") String authorization, #Body GitHubUpdateFileRequest request);
And I call that interface like this:
githubClient.updateConfigFile("token yourGeneratedGithubToken", request);
And yes, you do need the prefix "token ". You could hardcode that header into the interface, but I pass it in so that I can store it in locations outside of my version control's reach for security reasons.
In my spring boot project, one of my Service depends on external service like Amazon. I am writing the integration testing of the Controller classes. So, I want to mock the method in the AmazonService class(as it depends on third party API). The method is void with a single Long argument and can throw a custom application-specific exceptions.
The method is as follows:-
class AmazonService{
public void deleteMultipleObjects(Long enterpriseId) {
String key = formApplicationLogokey(enterpriseId,null);
List<S3ObjectSummary> objects = getAllObjectSummaryByFolder(key);
List<DeleteObjectsRequest.KeyVersion> keys = new ArrayList<>();
objects.stream().forEach(object->keys.add(new DeleteObjectsRequest.KeyVersion(object.getKey())));
try{
DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(this.bucket).withKeys(keys);
this.s3client.deleteObjects(deleteObjectsRequest);
log.debug("All the Application logos deleted from AWS for the Enterprise id: {}",enterpriseId);
}
catch(AmazonServiceException e){
throw new AppScoreException(AppScoreErrorCode.OBJECT_NOT_DELETED_FROM_AWS);
}
}}
class Test
class Test
{
#Autowired
AmazonServiceImpl amazonService;
#Autowired
EnterpriseService enterpriseService;
#Before
public void init()
{
amazonService = Mockito.mock(AmazonServiceImpl.class);
Mockito.doNothing().when(amazonService).deleteMultipleObjects(isA(Long.class));
}
#Test
public void testDeleteEnterprise(){
setHeaders();
EnterpriseDTO enterpriseDTO = createEnterpriseEntity(null,"testDeleteEnterpriseName3",null,null,null);
String postUrl = TestUrlUtil.createURLWithPort(TestConstants.ADD_ENTERPRISE,port);
HttpEntity<EnterpriseDTO> request1 = new HttpEntity<>(enterpriseDTO,headers);
ResponseEntity<EnterpriseDTO> response1 = restTemplate.postForEntity(postUrl,request1,EnterpriseDTO.class);
assert response1 != null;
Long enterpriseId = Objects.requireNonNull(response1.getBody()).getId();
String url = TestUrlUtil.createURLWithPort(TestConstants.DELETE_ENTERPRISE,port)+File.separator+enterpriseId;
HttpEntity<EnterpriseDTO> request = new HttpEntity<>(null, headers);
ResponseEntity<Object> response = restTemplate.exchange(url,HttpMethod.DELETE,request,Object.class);
Assert.assertEquals(Constants.ENTERPRISE_DELETION_SUCCESS_MESSAGE,response.getBody());
}
}
class EnterpriseResource
class EnterpriseResource
{
#DeleteMapping("/enterprises/{enterpriseId}")
public ResponseEntity<Object> deleteEnterprise(#PathVariable Long enterpriseId) {
log.debug("REST request to delete Enterprise : {}", enterpriseId);
enterpriseService.delete(enterpriseId);
return ResponseEntity.badRequest().body(Constants.ENTERPRISE_DELETION_SUCCESS_MESSAGE);
}
}
class EnterpriseServiceImpl
class EnterpriseServiceImpl
{
#Override
public void delete(Long enterpriseId) {
log.debug("Request to delete Enterprise : {}", enterpriseId);
enterpriseRepository.deleteById(enterpriseId);
amazonService.deleteMultipleObjects(enterpriseId);
}
}
I have tried various approaches to Mock this method but it didn't work and control is going inside this method during debugging. I want to do nothing in this method during testing.
I have tried the various approaches like throw(), doNothing(), spy() etc.
Please help what is missing here?
Thanks
I'm trying to find some manual how to test POST methods using jersey framework, only got examples for GET method.
Here's example:
#POST
#Path("add")
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.APPLICATION_XML)
public Response addUser(JAXBElement<User> user) {
int code = userService.addUser(user.getValue());
if (code == 500) {
return Response.status(500).build();
}
return Response.status(code).entity(user).build();
}
Could you please post some POST method test example?
Thank you in advance.
After research I did it!
Here's my solution, it works just fine.
And it's rather integration test, but we can write unit tests in similar manner.
public class RestTest extends JerseyTest{
#Override
protected Application configure() {
return new Your_Resource_Config(); //Your resource config with registered classes
}
//#Before and/or #After for db preparing etc. - if you want integration tests
#Test
public void addUserTest() {
User user = new User();
user.setEmail("user2#mail.com");
user.setName("Jane Doe");
user.getUserRoles().getRoles().add("supertester");
Entity<User> userEntity = Entity.entity(user, MediaType.APPLICATION_XML_TYPE);
target("users/add").request().post(userEntity); //Here we send POST request
Response response = target("users/find").queryParam("email", "user2#mail.com").request().get(); //Here we send GET request for retrieving results
Assert.assertEquals("user2#mail.com", response.readEntity(User.class).getEmail());
}
A test case for my contact formular page is to make sure it's always in a secure context respectively using SSL. Basically, all I want to know, is that I have a given request where request.secure = true;
The following response does not contain any information about this and its headers are empty:
#Test
public void shouldShowContactForm() {
Response response = GET("/contact");
// How can I ask the response, if the complete URL is in HTTPS?
}
Even if I explicitly set my own request, I cant see the right way to do this:
#Test
public void shouldShowContactFormInSSLContext() {
Request request = newRequest();
request.secure = true;
Response response = GET(request, "/contact");
// Is it now possible?
}
Is this even the right approach to test this or am I simply missing something important about the request/response?
For this question I think what I've done for my apps in the past is have a #before interceptor on all my controllers that looks like this.
#Before
static void checkSSL(){
if(Play.mode.equals(Play.Mode.PROD)){
if(!request.secure) {
Router.ActionDefinition cashTicketDefinition = Router.reverse(request.controller + "." + request.actionMethod);
cashTicketDefinition.absolute();
String url = cashTicketDefinition.url.replaceFirst( "http:", "https:");
redirect(url, true);
}
}
}