Quantcast
Channel: Java Team at Kentor » Frameworks
Viewing all articles
Browse latest Browse all 26

Testing Web layer with Selenium

$
0
0

Go back to Index

This is a follow up from step-5-creating-jpa-layer-using-java-ee

In this chapter we will add a login web page and write test cases towards it.
We will be using Arquillian and Selenium for this.
There are two parts of Selenium that is of interest of us here,
1. Selenium Server, server api
2. DefaultSelenium, client api

The goal here is to login with a username and password. Verify with the database that  you are allowed to login. And depending on the result we will either redirect you to a home page or back to the login page.
The Arquillian/Selenium test case will verify whether or not you successfully logged in.

Lets start by adding some dependencies, open the pom file and in the <dependecyManagement> section add a dependency to the arquillian-drone

<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-bom</artifactId>
<version>1.1.1.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>

Next we will add dependencies to arquillian drone and selenium. these are for all profiles. Add the following.

<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-selenium</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-selenium-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-server</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.mortbay.jetty</groupId>
<artifactId>servlet-api-2.5</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.5</version>
<scope>test</scope>
</dependency>

Since we had the org.slf4j dependency already defined in one of our previous profiles, we can move it out of the profile and put it accessible to all profiles.

We will start with the test case. Create a new Arquillian test called SecurityControllerTest, we will only have one test in here, we want to be able to login.

package com.bekkestad.examples.tutorial;

@RunWith(Arquillian.class)
public class SecurityControllerTest {

@Deployment(testable=false)
public static Archive<?> createDeployment(){
return null;
}

@Drone
DefaultSelenium browser;

@Test
@InSequence(1)
@RunAsClient
public void should_be_able_to_login(){
}
}

As you can see this test is very similar to the other ones we have created, but if you look more closely you will see a couple of differences (see bolded text). We are going to use Selenium together with Arquillian. Selenium is a client side test and not a server side. This means that we need to tell Arquillian this.

Let look a little at what we will be needing.
We need to have a user stored in the database, so we will need a UserEntity, that means we will also have a UserDTO. Then we need to have a way to access the Entity. This will be done using a repository. So that means we need to have a UserRepository and a UserRepositoryImpl.
Other than that since we are communicating up to a web layer we should have a service meaning we need a UserService and UserServiceImpl and a model. And finally we need to have some kind of controller class.
We have covered all this in previous posts so i am going to keep this short.

Here is the UserEntity

package com.bekkestad.examples.tutorial;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Transient;

@Entity
@NamedQueries({
@NamedQuery(
name=UserEntity.FIND_ALL,
query=”select u from UserEntity u”),
@NamedQuery(
name=UserEntity.FIND_USER_BY_USERNAME,
query=”select u from UserEntity u WHERE u.username = :username”),
@NamedQuery(
name=UserEntity.FIND_USER_BY_USERNAME_PASSWORD,
query=”select u from UserEntity u WHERE u.username = :username and u.password = :password”)
})
public class UserEntity {

public static final String FIND_ALL = “findAll”;
public static final String FIND_USER_BY_USERNAME = “findUserByUsername”;
public static final String FIND_USER_BY_USERNAME_PASSWORD = “findUserByUsernamePassword”;

@Id
@GeneratedValue
private Long id;
private String username;
private String password;

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
UserEntity other = (UserEntity) obj;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}

public Long getId() {
return id;
}

public String getPassword() {
return password;
}

public String getUsername() {
return username;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((username == null) ? 0 : username.hashCode());
return result;
}

public void setId(Long id) {
this.id = id;
}

public void setPassword(String password) {
this.password = password;
}

public void setUsername(String username) {
this.username = username;
}

@Override
public String toString() {
return “UserEntity [id=" + id + ", username=" + username + "]“;
}

@Transient
public UserDTO asDTO(){
UserDTO userDTO = new UserDTO();
userDTO.setUserId(this.id);
userDTO.setUsername(this.username);
userDTO.setPassword(this.password);
return userDTO;
}

public static UserEntity fromDTO(UserDTO userDTO){
UserEntity userEntity = new UserEntity();
userEntity.setId(userDTO.getUserId());
userEntity.setUsername(userDTO.getUsername());
userEntity.setPassword(userDTO.getPassword());

return userEntity;
}

}

UserDTO
package com.bekkestad.examples.tutorial;
public class UserDTO {
private Long userId;
private String username;
private String password;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
UserRepository
package com.bekkestad.examples.tutorial;
import java.util.List;
import javax.ejb.Local;
@Local
public interface UserRepository {
List<UserDTO> getAllUsers();
UserDTO getUser(String username);
UserDTO getUser(String username, String password);
Long addUser(UserDTO user);
void updateUser(UserDTO user);
void deleteUser(UserDTO user);
}
UserRepositoryImpl
package com.bekkestad.examples.tutorial;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
@Singleton
@Lock(LockType.READ)
public class UserRepositoryImpl implements UserRepository {
@PersistenceContext
EntityManager em;
@Override
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public List<UserDTO> getAllUsers() {
List<UserDTO> userDTOList = new ArrayList<UserDTO>();
@SuppressWarnings(“unchecked”)
List<UserEntity> users =  em.createNamedQuery(UserEntity.FIND_ALL).getResultList();
for(UserEntity user : users){
userDTOList.add(user.asDTO());
}
return userDTOList;
}
@Override
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public UserDTO getUser(String username) throws NoResultException {
UserEntity userEntity = null;
try{
userEntity = (UserEntity)em.createNamedQuery(UserEntity.FIND_USER_BY_USERNAME)
.setParameter(“userName”, username).getSingleResult();
}catch(NoResultException nre){
throw new NoResultException(“NO USER”);
}catch(Exception e){
e.printStackTrace();
}
return userEntity!=null?userEntity.asDTO():null;
}
@Override
// @TransactionAttribute(TransactionAttributeType.SUPPORTS)
public UserDTO getUser(String username, String password) throws NoResultException {
UserEntity userEntity = null;
try{
userEntity = (UserEntity)em.createNamedQuery(UserEntity.FIND_USER_BY_USERNAME_PASSWORD)
.setParameter(“username”, username)
.setParameter(“password”, password)
.getSingleResult();
}catch(NoResultException nre){
throw new NoResultException(“NO USER”);
}catch(Exception e){
e.printStackTrace();
}
return userEntity!=null?userEntity.asDTO():null;
}
@Override
@Lock(LockType.WRITE)
// @TransactionAttribute(TransactionAttributeType.SUPPORTS)
public Long addUser(UserDTO userDTO) {
UserEntity userEntity = UserEntity.fromDTO(userDTO);
try {
em.persist(userEntity);
em.flush();
} catch (Exception e) {
e.printStackTrace();
}
return userEntity.getId();
}
@Override
@Lock(LockType.WRITE)
// @TransactionAttribute(TransactionAttributeType.SUPPORTS)
public void updateUser(UserDTO userDTO) {
em.merge(UserEntity.fromDTO(userDTO));
em.flush();
}
@Override
@Lock(LockType.WRITE)
// @TransactionAttribute(TransactionAttributeType.SUPPORTS)
public void deleteUser(UserDTO userDTO) {
em.remove(UserEntity.fromDTO(userDTO));
em.flush();
}
}
UserService
package com.bekkestad.examples.tutorial;
import java.util.List;
public interface UserService {
void addUser(UserDTO user);
void updateUser(UserDTO user);
void removeUser(UserDTO user);
UserDTO getUser(String username) throws Exception;
UserDTO loginUser(String username, String password);
List<UserDTO> getUsers();
}

UserServiceImpl

package com.bekkestad.examples.tutorial;

import java.io.Serializable;
import java.util.List;

import javax.ejb.EJB;
import javax.enterprise.context.SessionScoped;

@SessionScoped
public class UserServiceImpl implements UserService, Serializable {

private static final long serialVersionUID = 1L;

@EJB
private UserRepository users;

public void addUser(UserDTO user){
users.addUser(user);
}

public void updateUser(UserDTO user){
users.updateUser(user);
}

public void removeUser(UserDTO user){
users.deleteUser(user);
}

public UserDTO getUser(String username) throws Exception{
return users.getUser(username);
}

public UserDTO loginUser(String username, String password){
return users.getUser(username, password);
}

public List<UserDTO> getUsers(){
return users.getAllUsers();
}

}

Ok… So far we have created the entity and the corresponding DTO.
We have a repository and a service…
The reason for this is separation. In a future post we will look into multi modules and this separation will make much more sense. But a simple explanation is that we want to keep database layer together and front end layer together.

Basically, my controller know where is a service to get my user from, he does not care if that is from a database, flat file, another external service, active directory/LDAP etc…
The service knows that he should get the user from a Repository, but it does not know any specifics about it.
And finally the Repository has access to an EntityManager and is able to query the database.

Now it is time to create the Model, this is a class that will communicate with the web page, lets call this model Credentials, we could have used the DTO here too, but i want to separate the two.

package com.bekkestad.examples.tutorial;

import javax.enterprise.inject.Model;

@Model
public class Credentials {

private String username;
private String password;

public String getPassword() {
return password;
}

public String getUsername() {
return username;
}

public void setPassword(String password) {
this.password = password;
}

public void setUsername(String username) {
this.username = username;
}
}

Finally we need to have a Controller, lets call it SecurityController

package com.bekkestad.examples.tutorial;

import java.io.Serializable;

import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Produces;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@SessionScoped
public class SecurityController implements Serializable {

private static final long serialVersionUID = 1L;

private static final String SUCCESS_MESSAGE = “Welcome”;
private static final String FAILURE_MESSAGE = “Incorrect username and/or password”;

private UserDTO currentUser;

@Inject
private Credentials credentials;

@Inject
private UserServiceImpl userService;

public String login(){

String nextState = “home.xhtml”;
if(!isLoggedIn()){
try {

currentUser = userService.loginUser(credentials.getUsername(), credentials.getPassword());
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(SUCCESS_MESSAGE));

} catch (Exception e) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_WARN, FAILURE_MESSAGE, FAILURE_MESSAGE));
nextState = “login.xhtml”;
}
}
return nextState;
}

public boolean isLoggedIn(){
return currentUser != null;
}

@Produces
@Named
public UserDTO getCurrentUser(){
return currentUser;
}
}

All this does is to provide us with a login method. we are able to tie the html form tags to our Model and get the text entered. We will use that to do a login attempt, depending on the result we will be logged in or redirected back to login page.
Before we are ready, we need to create a couple more things.
Lets start by creating a new source folder called webapp. This should be under “src/main/webapp”
In there we need two files, login.xhtml and home.xhtml
login.xhtml
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”&gt;
<head>
<title>Please login</title>
</head>
<body>
<h1>Please login</h1>
<h:messages />
<h:form  id=”loginForm”>
<h:panelGrid columns=”2″>
<h:outputLabel for=”username”>Username:</h:outputLabel>
<h:inputText id=”username” value=”#{credentials.username}” />
<h:outputLabel for=”password”>Password</h:outputLabel>
<h:inputSecret id=”password” value=”#{credentials.password}” />
<h:commandButton id=”login” action=”#{securityController.login}” value=”Log in” />
</h:panelGrid>
</h:form>
</body>
</html>
home.xhtml
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”&gt;
<head>
<title>Home</title>
</head>
<body>
<h:messages />
<h:panelGroup rendered=”#{securityController.loggedIn}”>
<h1>You are signed in as #{currentUser.username}.</h1>
</h:panelGroup>
</body>
</html>

Finally, lets go back to our Test and add some functionality…
We need to package the deployment, create a new ShrinkWrap, it should be a WebArchive and call it “login.war”. Once that is done we need to add all the classes that are needed for this test, these are
UserEntity, UserDTO, UserRepository, UserRepositoryImpl, UserService, UserServiceImpl, Credentials and Securitycontroller.
After that we should add our persistence.xml, the jboss datasource and beans.xml
The createDeployment should look like this now.

@Deployment(testable=false)
public static Archive<?> createDeployment(){
return ShrinkWrap.create(WebArchive.class, “login.war”)
.addClasses(UserEntity.class, UserDTO.class, UserRepository.class, UserRepositoryImpl.class, UserService.class, UserServiceImpl.class, Credentials.class, SecurityController.class)
.addAsResource(“test-persistence.xml”, “META-INF/persistence.xml”)
.addAsWebInfResource(“jbossas-ds.xml”)
.addAsWebInfResource(EmptyAsset.INSTANCE, “beans.xml”)
}

But is this enough?
Remember that we created a new source folder and that we added some files to it?
So we need to add these to the deployment too.
Start by creating a new constant to the WEBAPP_SRC
private static final String WEBAPP_SRC = “src/main/webapp”;
Then add the two web pages as WebResources

.addAsWebResource(new File(WEBAPP_SRC, “login.xhtml”))
.addAsWebResource(new File(WEBAPP_SRC, “home.xhtml”))

There is one more setting we need for the web pages to work, we need to add a faces-config.xml file, and unlike the beans.xml this needs to have text in it. So go ahead and add

.addAsWebInfResource(new StringAsset(“<faces-config version=\”2.0\”/>”),”faces-config.xml”);

We have now created the deployment. So lets focus on the test.
Selenium will allow us to open a browser, go to a url, and traverse over the InnerHTML and do things to it.
What we will do is open the login.jsf page, find the username and password and enter some valid test data there and will will press the submit button.
This will be sent to our SecurityController, which will generate a result, which should be the home page or back to the login page.
Once we are back into the test we will inspect the returned page and look for some known constants using xpath and that will be asserted by arquillian.
The method should look like this

@Test
@InSequence(1)
@RunAsClient
public void should_be_able_to_login(@ArquillianResource URL deploymentUrl){
browser.open(deploymentUrl + “login.jsf”);

browser.type(“id=loginForm:username”, “demo”);
browser.type(“id=loginForm:password”, “password”);
browser.click(“id=loginForm:login”);
browser.waitForPageToLoad(“15000″);

Assert.assertTrue(“User should be logged in!”,
browser.isElementPresent(“xpath=//li[contains(text(), 'Welcome')]“));
Assert.assertTrue(“Username should be shown!”,
browser.isElementPresent(“xpath=//h1[contains(text(), 'You are signed in as demo.')]“));
}

There is one more thing i want to talk about before we do the test.
As you see in this test method we are submitting the user “demo” with the password “password” and we are testing that he is able to login.
If you were to run this test as it is now, you would get a fail on this test, since the UserService would not be able to log in the user demo. The database we are using is an in memory database that is recreated constantly. So we need to have a way to make sure that this user is in the database before we run the test.
We are going to do this by creating a new file called “test-import.sql” go ahead and do one for each container. So create a new file under src/test/resources/glassfish-embedded and src/test/resources/jboss-managed.
The file should look like this.

delete from UserEntity;
insert into UserEntity(id, username, password) values (1, ‘demo’, ‘password’);

We want to have a file import.sql in our src/test/resources, because this will run before the EntityManager is created and therefor our user will be in the database.
So the very last thing we need to do is to add this file to our createDeployment method.

.addAsResource(“test-import.sql”, “import.sql”)

We are now ready to test.
To get the source code you can go to GitHub
If you were to look at the current code you will see that it is quite “ugly”. There a few reasons for that, first i wanted to keep the tutorial simple and only talk about the specifics in each subject. And i also wanted to open for talking about architecture and design. Which will be in the next session.

Go back to Index



Viewing all articles
Browse latest Browse all 26

Trending Articles