datasource-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"
p:maxActive="${dbcp.maxActive}"
p:maxIdle="${dbcp.maxIdle}"
p:maxWait="${dbcp.maxWait}"/>
</beans>
jdbc-live.properties
jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@my_server:1521:my_db
jdbc.username=my_user
jdbc.password=my_password
dbcp.maxActive=100
dbcp.maxIdle=30
dbcp.maxWait=20000
hibernate.generate_statistics=true
hibernate.show_sql=false
hibernate.format_sql=false
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
jdbc-test.properties
jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@my_server:1521:my_db
jdbc.username=my_user
jdbc.password=my_password
dbcp.maxActive=100
dbcp.maxIdle=30
dbcp.maxWait=20000
hibernate.generate_statistics=true
hibernate.show_sql=false
hibernate.format_sql=false
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
jdbc.properties
jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@my_server:1521:my_db
jdbc.username=my_user
jdbc.password=my_password
dbcp.maxActive=100
dbcp.maxIdle=30
dbcp.maxWait=20000
hibernate.generate_statistics=true
hibernate.show_sql=true
hibernate.format_sql=false
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
jpa-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:component-scan base-package="com.lishman.qa.data.jpa"/>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:persistenceUnitName="QA"
p:dataSource-ref="dataSource">
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
p:databasePlatform="${hibernate.dialect}"
p:showSql="${hibernate.show_sql}"/>
</property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="jpaTemplate" class="org.springframework.orm.jpa.JpaTemplate">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
</beans>
messages.properties
application.name=Questions & Answers
theme.plain=plain
theme.silver=silver
theme.blue=blue
navigation.back=back
navigation.cancel=cancel
navigation.menu.main=menu
button.create=Create
button.update=Update
button.delete=Delete
test.take=Take a Test
test.nextQuestion=next question
question.label=Question
question.plural=Questions
question.details=Question Details
question.maintain=Question Maintenance
question.revealAnswer=Reveal Answer
answer.label=Answer
tag.label=Tag
tag.plural=Tags
tag.details=Tag Details
tag.maintain=Tag Maintenance
tag.field.name=Name
tag.field.description=Description
validation.exists=already exists
validation.hasQuestions=tag is being used by {0} question(s)
typeMismatch.java.util.Date=must be a valid date
typeMismatch.java.lang.Integer=must be a valid number
typeMismatch.java.lang.Long=must be a valid number
NotEmpty=must not be empty
NotBlank=must not be empty
messages_de.properties
application.name=Fragen und Antworten
theme.plain=plain
theme.silver=silber
theme.blue=blau
navigation.back=startseite
navigation.cancel=stornieren
navigation.menu.main=menü
button.create=Schaffen
button.update=Unter
button.delete=Löschen
test.take=Werfen Sie einen Test
test.nextQuestion=nächste frage
question.label=Frage
question.plural=Fragen
question.details=Frage Details
question.maintain=Frage Wartung
question.revealAnswer=Reveal Antwort
answer.label=Antwort
tag.label=Tag
tag.plural=Tags
tag.details=Tag Details
tag.maintain=Tag Wartung
tag.field.name=Name
tag.field.description=Beschreibung
validation.exists=bereits vorhanden
validation.hasQuestions=begriffe in gebrauch
typeMismatch.java.util.Date=ungültiges Datum
typeMismatch.java.lang.Integer=ungültige Zahl
typeMismatch.java.lang.Long=ungültige Zahl
NotEmpty=darf nicht leer sein
NotBlank=darf nicht leer sein
messages_fr.properties
application.name=Questions et réponses
theme.plain=plaine
theme.silver=d'argent
theme.blue=bleu
navigation.back=retournez
navigation.cancel=annuler
navigation.menu.main=menu
button.create=Créer
button.update=Changement
button.delete=Supprimer
test.take=Passer un Test
test.nextQuestion=question suivante
question.label=Question
question.plural=Questions
question.details=Détails Question
question.maintain=Maintenance Question
question.revealAnswer=Révèlent Réponse
answer.label=Réponse
tag.label=Tag
tag.plural=Tags
tag.details=Détails de Tag
tag.maintain=Tag Maintenance
tag.field.name=Nom
tag.field.description=Description
validation.exists=Existe déjà
validation.hasQuestions=Tag en cours d'utilisation
typeMismatch.java.util.Date=date non valide
typeMismatch.java.lang.Integer=nombre invalide
typeMismatch.java.lang.Long=nombre invalide
NotEmpty=Ne doit pas être vide
NotBlank=Ne doit pas être vide
plain.properties
styleSheet=/qa1/css/plain.css
silver.properties
styleSheet=/qa1/css/silver.css
domain-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:component-scan base-package="com.lishman.qa.domain"/>
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
</beans>
log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
#------------------------------------------------------------------------------
# Root
log4j.rootLogger=INFO, stdout
#------------------------------------------------------------------------------
# Application
com.lishman=INFO
#------------------------------------------------------------------------------
# Spring
log4j.logger.org.springframework=INFO
#------------------------------------------------------------------------------
# Hibernate
log4j.logger.org.hibernate=INFO
### log HQL query parser activity
#log4j.logger.org.hibernate.hql.ast.AST=debug
### log just the SQL
log4j.logger.org.hibernate.SQL=INFO
### log JDBC bind parameters ###
l#og4j.logger.org.hibernate.type=DEBUG
### log schema export/update ###
#log4j.logger.org.hibernate.tool.hbm2ddl=info
### log HQL parse trees
#log4j.logger.org.hibernate.hql=INFO
### log cache activity ###
#log4j.logger.org.hibernate.cache=info
### log transaction activity
#log4j.logger.org.hibernate.transaction=debug
### log JDBC resource acquisition
#log4j.logger.org.hibernate.jdbc=debug
### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace
mvc-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:annotation-driven/>
<context:component-scan base-package="com.lishman.qa.web"/>
<mvc:view-controller path="/" view-name="main-menu"/>
<mvc:view-controller path="/main-menu.html" view-name="main-menu"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp"/>
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
p:basename="classpath:messages/messages"/>
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
</mvc:interceptors>
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver"
p:cookieName="MyLocale"/>
<bean id="themeSource"
class="org.springframework.ui.context.support.ResourceBundleThemeSource"
p:basenamePrefix="/themes/"/>
<mvc:interceptors>
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"
p:paramName="theme-name"/>
</mvc:interceptors>
<bean id="themeResolver"
class="org.springframework.web.servlet.theme.CookieThemeResolver"
p:defaultThemeName="plain"/>
</beans>
service-config-with-stubs.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<context:component-scan base-package="com.lishman.qa.service">
<context:exclude-filter type="regex" expression="com.lishman.qa.service.*Impl"/>
</context:component-scan>
</beans>
service-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:component-scan base-package="com.lishman.qa.service">
<context:exclude-filter type="regex" expression="com.lishman.qa.service.*Stub"/>
</context:component-scan>
</beans>
Testing.txt
Testing the stack
Browser
|
Web
|
Service
|
Data
|
Database
Layers are tested by mocking (eg using Mockito) the layer
below, except for the data layer which is tested against the
real database and is considered an integration test.
We are talking real TDD tests here to ensure that the tests
drive the design. See http://blog.stevensanderson.com/2009/08/24/writing-great-unit-tests-best-and-worst-practises/
for a great blog called 'Writing Great Unit Tests: Best and Worst Practices'.
The browser / web interaction is also tested using TDD.
Tests are performed using selenium 2 using real browser
with a the application running in web server.
I believe that this is the only way to really test the web
layer and minimises the chance of subtle difference
introduced by 'pretend' implementations such as HtmlUnit (?)
Browser testing mocks the layer below in the same way as the
other layers do. In this case the service layer is mocked using
a stub. A stub is a bespoke implementation of the service which can
save and return data, as opposed to a mock object using a mocking
framework like Mockito. See http://martinfowler.com/articles/mocksArentStubs.html
by Martin Fowler for a convenient definition of the differences
between mocks, stubs, fakes and dummies.
This stub is then accessed and controlled by the unit test using
a separate controller which provides a REST interface.
schema.sql
DROP TABLE question_tag;
DROP TABLE question;
DROP TABLE tag;
DROP SEQUENCE tag_seq1;
CREATE TABLE tag (
tag_id NUMBER,
tag_name VARCHAR2(20) NOT NULL,
tag_desc VARCHAR2(1000),
CONSTRAINT tag_pk PRIMARY KEY (tag_id),
CONSTRAINT tag_uk1 UNIQUE (tag_name),
CONSTRAINT tag_cc1 CHECK (trim(tag_name) IS NOT NULL)
);
CREATE SEQUENCE tag_seq1;
CREATE OR REPLACE TRIGGER tag_trg1
BEFORE INSERT ON tag FOR EACH ROW
BEGIN
IF :NEW.tag_id IS NULL THEN
SELECT tag_seq1.NEXTVAL INTO :NEW.tag_id FROM DUAL;
END IF;
END;
/
DROP SEQUENCE question_seq1;
CREATE TABLE question (
ques_id NUMBER,
question VARCHAR2(4000) NOT NULL,
answer VARCHAR2(4000) NOT NULL,
CONSTRAINT question_pk PRIMARY KEY (ques_id),
CONSTRAINT question_cc1 CHECK (trim(question) IS NOT NULL),
CONSTRAINT question_cc2 CHECK (trim(answer) IS NOT NULL)
);
CREATE SEQUENCE question_seq1;
CREATE OR REPLACE TRIGGER question_trg1
BEFORE INSERT ON question FOR EACH ROW
BEGIN
IF :NEW.ques_id IS NULL THEN
SELECT question_seq1.NEXTVAL INTO :NEW.ques_id FROM DUAL;
END IF;
END;
/
CREATE TABLE question_tag (
ques_id NUMBER,
tag_id NUMBER,
CONSTRAINT question_tag_pk PRIMARY KEY (ques_id, tag_id),
CONSTRAINT question_tag_fk1 FOREIGN KEY (ques_id) REFERENCES question,
CONSTRAINT question_tag_fk2 FOREIGN KEY (tag_id) REFERENCES tag
);
unit-test-data.sql
DROP SEQUENCE tag_seq1;
CREATE SEQUENCE tag_seq1 START WITH 100;
DROP SEQUENCE question_seq1;
CREATE SEQUENCE question_seq1 START WITH 100;
TRUNCATE TABLE question_tag;
DELETE FROM tag;
DELETE FROM question;
COMMIT;
INSERT INTO tag (tag_id, tag_name, tag_desc) VALUES (1, 'First Tag', 'Description of First Tag');
INSERT INTO tag (tag_id, tag_name, tag_desc) VALUES (2, 'Second Tag', 'Description of Second Tag');
INSERT INTO tag (tag_id, tag_name, tag_desc) VALUES (3, 'Third Tag', 'Description of Third Tag');
INSERT INTO tag (tag_id, tag_name, tag_desc) VALUES (4, 'Fourth Tag', 'Description of Fourth Tag');
INSERT INTO question (ques_id, question, answer) VALUES (1, 'First question?', 'Answer to first question');
INSERT INTO question (ques_id, question, answer) VALUES (2, 'Second question?', 'Answer to second question');
INSERT INTO question (ques_id, question, answer) VALUES (3, 'Third question?', 'Answer to third question');
INSERT INTO question (ques_id, question, answer) VALUES (4, 'Fourth question?', 'Answer to fourth question');
INSERT INTO question (ques_id, question, answer) VALUES (5, 'Fifth question?', 'Answer to fifth question');
INSERT INTO question_tag (ques_id, tag_id) VALUES (1, 1);
INSERT INTO question_tag (ques_id, tag_id) VALUES (2, 1);
INSERT INTO question_tag (ques_id, tag_id) VALUES (2, 2);
INSERT INTO question_tag (ques_id, tag_id) VALUES (3, 1);
INSERT INTO question_tag (ques_id, tag_id) VALUES (3, 2);
INSERT INTO question_tag (ques_id, tag_id) VALUES (3, 3);
COMMIT;
JpaQuestionDao.java
package com.lishman.qa.data.jpa;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.Errors;
import com.lishman.qa.data.QuestionDao;
import com.lishman.qa.domain.DomainValidator;
import com.lishman.qa.domain.Question;
@Repository
@Transactional
public class JpaQuestionDao implements QuestionDao {
@Autowired
JpaTemplate jpaTemplate;
@Autowired
private DomainValidator questionValidator;
@Transactional(readOnly = true)
public Question getById(int questionId) {
return (Question) jpaTemplate.find(Question.class, questionId);
}
@SuppressWarnings("unchecked")
@Transactional(readOnly = true)
public List<Question> getAll() {
return jpaTemplate.findByNamedQuery("Question.findAll");
}
@Override
public Question save(Question question, Errors errors) {
questionValidator.validate(question, errors);
if (errors.hasErrors()) {
return null;
}
return jpaTemplate.merge(question);
}
@Override
public void delete(Question question) {
jpaTemplate.remove(getById(question.getId()));
}
}
JpaTagDao.java
package com.lishman.qa.data.jpa;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.Errors;
import com.lishman.qa.data.TagDao;
import com.lishman.qa.domain.DomainValidator;
import com.lishman.qa.domain.Tag;
@Repository
@Transactional
public class JpaTagDao implements TagDao {
@Autowired
JpaTemplate jpaTemplate;
@Autowired
private DomainValidator tagValidator;
@SuppressWarnings("unchecked")
@Transactional(readOnly = true)
public List<Tag> getAll() {
return jpaTemplate.findByNamedQuery("Tag.findAll");
}
@Transactional(readOnly = true)
public Tag getById(int tagId) {
return (Tag) jpaTemplate.find(Tag.class, tagId);
}
@SuppressWarnings("unchecked")
@Transactional(readOnly = true)
public Tag getByName(String name) {
Map<String, String> params = new HashMap<String, String>();
params.put("tag_name", name);
List<Tag> tags = jpaTemplate.findByNamedQueryAndNamedParams("Tag.findByName", params);
return tags.isEmpty() ? null : tags.get(0);
}
public Tag save(Tag tag, Errors errors) {
tagValidator.validate(tag, errors);
validateNameNotInUse(tag, errors);
if (errors.hasErrors()) {
return null;
}
return jpaTemplate.merge(tag);
}
public void delete(Tag tag, Errors errors) {
int questionCount = tag.getQuestions().size();
if (questionCount > 0) {
errors.reject("validation.hasQuestions", new Object[] {questionCount}, "has questions");
return;
}
jpaTemplate.remove(getById(tag.getId()));
}
private void validateNameNotInUse(Tag tag, Errors errors) {
if (!errors.hasFieldErrors("name")) {
Tag existingTag = this.getByName(tag.getName());
if (existingTag != null
&& (tag.isNew() ||
!tag.getId().equals(existingTag.getId()))) {
errors.rejectValue("name", "validation.exists", "exists");
}
}
}
}
QuestionDao.java
package com.lishman.qa.data;
import java.util.List;
import org.springframework.validation.Errors;
import com.lishman.qa.domain.Question;
public interface QuestionDao {
public List<Question> getAll();
public Question getById(int questionId);
public Question save(Question question, Errors errors);
public void delete(Question question);
}
TagDao.java
package com.lishman.qa.data;
import java.util.List;
import org.springframework.validation.Errors;
import com.lishman.qa.domain.Tag;
public interface TagDao {
public List<Tag> getAll();
public Tag getById(int tagId);
public Tag getByName(String tagName);
public Tag save(Tag tag, Errors errors);
public void delete(Tag tag, Errors errors);
}
DomainValidator.java
package com.lishman.qa.domain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
@Component
public class DomainValidator {
private SpringValidatorAdapter validatorAdapter;
@Autowired
@Qualifier("validator")
public void setValidatorAdapter(SpringValidatorAdapter validator) {
this.validatorAdapter = validator;
}
public void validate(Object object, Errors errors) {
validatorAdapter.validate(object, errors);
}
}
Question.java
package com.lishman.qa.domain;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.hibernate.annotations.Sort;
import org.hibernate.annotations.SortType;
import org.hibernate.validator.constraints.NotBlank;
@Entity
@Table(name = "QUESTION")
@NamedQueries ({
@NamedQuery(name="Question.findAll", query="select q from Question q order by q.id"),
@NamedQuery(name="Question.findByName", query="select q from Question q where q.question = :question_name")
})
public class Question {
@Id
@GeneratedValue(generator = "QuestionSequence",
strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "QuestionSequence",
sequenceName = "QUESTION_SEQ1")
@Column(name = "QUES_ID")
private Integer id;
@Column(name = "QUESTION")
@NotBlank
private String question;
@Column(name = "ANSWER")
@NotBlank
private String answer;
@ManyToMany(fetch=FetchType.EAGER)
@JoinTable (name="QUESTION_TAG",
joinColumns = {@JoinColumn(name="QUES_ID")},
inverseJoinColumns={@JoinColumn(name="TAG_ID")}
)
@Sort(type=SortType.NATURAL)
private SortedSet<Tag> tags = new TreeSet<Tag>();
public Question() {}
public Question(String question, String answer) {
setQuestion(question);
setAnswer(answer);
}
@JsonIgnore
public boolean isNew() {
return id == null;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setQuestion(String question) {
this.question = question;
}
public String getQuestion() {
return question;
}
public void setAnswer(String answer) {
this.answer = answer;
}
public String getAnswer() {
return answer;
}
public void setTags(SortedSet<Tag> tags) {
this.tags = tags;
}
public SortedSet<Tag> getTags() {
return tags;
}
}
Tag.java
package com.lishman.qa.domain;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.hibernate.validator.constraints.NotBlank;
@Entity
@Table(name = "TAG")
@NamedQueries ({
@NamedQuery(name="Tag.findAll", query="select t from Tag t order by t.name"),
@NamedQuery(name="Tag.findByName", query="select t from Tag t where t.name = :tag_name")
})
public class Tag implements Comparable<Tag> {
@Id
@GeneratedValue(generator = "TagSequence",
strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "TagSequence",
sequenceName = "TAG_SEQ1")
@Column(name = "TAG_ID")
private Integer id;
@Column(name = "TAG_NAME")
@NotBlank
private String name;
@Column(name = "TAG_DESC")
private String description;
@ManyToMany(fetch=FetchType.EAGER, mappedBy="tags")
private List<Question> questions = new ArrayList<Question>();
public Tag() {
}
public Tag(String name, String description) {
setName(name);
setDescription(description);
}
@JsonIgnore
public boolean isNew() {
return id == null;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<Question> getQuestions() {
return questions;
}
public int compareTo(Tag otherTag) {
return this.getName().toLowerCase().compareTo(otherTag.getName().toLowerCase());
}
@Override
public String toString() {
return getName();
}
}
QaService.java
package com.lishman.qa.service;
import java.util.List;
import org.springframework.validation.Errors;
import com.lishman.qa.domain.Question;
import com.lishman.qa.domain.Tag;
public interface QaService {
public List<Question> getQuestions();
public List<Question> getShuffledQuestions();
public Question getQuestionById(int questionId);
public Question saveQuestion(Question question, Errors errors);
public void deleteQuestion(Question question);
public List<Tag> getTags();
public Tag getTagById(int tagId);
public Tag getTagByName(String name);
public Tag saveTag(Tag tag, Errors errors);
public void deleteTag(Tag tag, Errors errors);
}
QaServiceImpl.java
package com.lishman.qa.service;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.Errors;
import com.lishman.qa.data.QuestionDao;
import com.lishman.qa.data.TagDao;
import com.lishman.qa.domain.Question;
import com.lishman.qa.domain.Tag;
@Service
public class QaServiceImpl implements QaService {
private QuestionDao questionDao;
private TagDao tagDao;
@Autowired
public void setQuestionDao(QuestionDao questionDao) {
this.questionDao = questionDao;
}
@Autowired
public void setTagDao(TagDao tagDao) {
this.tagDao = tagDao;
}
@Override
public List<Question> getQuestions() {
return questionDao.getAll();
}
@Override
public List<Question> getShuffledQuestions() {
List<Question> questions = getQuestions();
Collections.shuffle(questions);
return questions;
}
@Override
public Question getQuestionById(int questionId) {
return questionDao.getById(questionId);
}
@Override
public Question saveQuestion(Question question, Errors errors) {
return questionDao.save(question, errors);
}
@Override
public void deleteQuestion(Question question) {
questionDao.delete(question);
}
@Override
public List<Tag> getTags() {
return tagDao.getAll();
}
@Override
public Tag getTagById(int tagId) {
return tagDao.getById(tagId);
}
@Override
public Tag getTagByName(String name) {
return tagDao.getByName(name);
}
@Override
public Tag saveTag(Tag tag, Errors errors) {
return tagDao.save(tag, errors);
}
@Override
public void deleteTag(Tag tag, Errors errors) {
tagDao.delete(tag, errors);
}
}
QuestionController.java
package com.lishman.qa.web;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import com.lishman.qa.domain.Question;
import com.lishman.qa.service.QaService;
@Controller
@SessionAttributes("questions")
public class QuestionController {
private QaService qaService;
@Autowired
public void setQaService(QaService qaService) {
this.qaService = qaService;
}
@RequestMapping("/question-list")
@ModelAttribute("questions")
public List<Question> getQuestions() {
return qaService.getQuestions();
}
@RequestMapping("/question-details/{id}")
public String askQuestion(@PathVariable("id") int questionId,
Model model) {
Question question = qaService.getQuestionById(questionId);
model.addAttribute(question);
return "question-details";
}
@SuppressWarnings("unchecked")
@RequestMapping("/ask-question")
public String askQuestion(Model model, SessionStatus status) {
if (!model.containsAttribute("questions")) {
model.addAttribute("questions", qaService.getShuffledQuestions());
}
List<Question> questions = (List<Question>) model.asMap().get("questions");
if (questions.isEmpty()) {
model.asMap().remove("questions");
status.setComplete();
return "main-menu";
}
Question question = questions.get(0);
model.addAttribute(question);
questions.remove(0);
return "ask-question";
}
}
QuestionForm.java
package com.lishman.qa.web;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import com.lishman.qa.domain.Question;
import com.lishman.qa.domain.Tag;
import com.lishman.qa.service.QaService;
@Controller
@RequestMapping("/question-form")
@SessionAttributes("question")
public class QuestionForm {
@Autowired
private QaService qaService;
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
dataBinder.registerCustomEditor(String.class, new StringTrimmerEditor(false));
dataBinder.registerCustomEditor(Tag.class, new TagPropertyEditor(qaService));
}
@ModelAttribute("tagList")
public List<Tag> getTagList () {
return (List<Tag>) qaService.getTags();
}
@RequestMapping(method = RequestMethod.GET)
public Question setUpForm(
@RequestParam(value = "id", required = false) Integer questionId) {
return questionId == null ? new Question() : qaService.getQuestionById(questionId);
}
@RequestMapping(params = "save", method = RequestMethod.POST)
public String save(Question question,
BindingResult result,
SessionStatus status) {
qaService.saveQuestion(question, result);
if (result.hasErrors()) {
return "question-form";
}
status.setComplete();
return "redirect:question-list";
}
@RequestMapping(params = "delete", method = RequestMethod.POST)
public String delete(Model model, SessionStatus status) {
Question question = (Question) model.asMap().get("question");
qaService.deleteQuestion(question);
status.setComplete();
return "redirect:question-list";
}
}
TagController.java
package com.lishman.qa.web;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import com.lishman.qa.domain.Tag;
import com.lishman.qa.service.QaService;
@Controller
public class TagController {
private QaService qaService;
@Autowired
public void setQaService(QaService qaService) {
this.qaService = qaService;
}
@RequestMapping("/tag-list")
@ModelAttribute("tags")
public final List<Tag> getTags() {
return qaService.getTags();
}
@RequestMapping("/tag-details/{id}")
public final String getTag(@PathVariable("id") int tagId,
Model model) {
Tag tag = qaService.getTagById(tagId);
model.addAttribute(tag);
return "tag-details";
}
}
TagForm.java
package com.lishman.qa.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import com.lishman.qa.domain.Tag;
import com.lishman.qa.service.QaService;
@Controller
@RequestMapping("/tag-form")
@SessionAttributes("tag")
public class TagForm {
@Autowired
private QaService qaService;
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
dataBinder.registerCustomEditor(String.class, new StringTrimmerEditor(false));
}
@RequestMapping(method = RequestMethod.GET)
public Tag setUpForm(@RequestParam(value = "id", required = false) Integer tagId) {
return tagId == null ? new Tag() : qaService.getTagById(tagId);
}
@RequestMapping(params = "save", method = RequestMethod.POST)
public String save(Tag tag,
BindingResult result,
SessionStatus status) {
qaService.saveTag(tag, result);
if (result.hasErrors()) {
return "tag-form";
}
status.setComplete();
return "redirect:tag-list";
}
@RequestMapping(params = "delete", method = RequestMethod.POST)
public String delete(Tag tag, BindingResult result, SessionStatus status) {
qaService.deleteTag(tag, result);
if (result.hasErrors()) {
return "tag-form";
}
status.setComplete();
return "redirect:tag-list";
}
}
TagPropertyEditor.java
package com.lishman.qa.web;
import java.beans.PropertyEditorSupport;
import com.lishman.qa.service.QaService;
public class TagPropertyEditor extends PropertyEditorSupport{
private QaService qaService;
public TagPropertyEditor(QaService qaService) {
this.qaService = qaService;
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(qaService.getTagByName(text));
}
}
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="QA" transaction-type="RESOURCE_LOCAL"/>
</persistence>
ApplicationPage.java
package com.lishman.qa.browser.pageobjects;
import org.openqa.selenium.WebDriver;
public class ApplicationPage extends Page{
public static final String BLACK = "#000000";
public static final String BLUE = "#0000ff";
public static final String DARK_SLATE_GRAY = "#2f4f4f";
public static final String DIM_GRAY = "#696969";
public static final String SEA_GREEN = "#2e8b57";
public ApplicationPage (WebDriver driver, String[] pageTitles) {
super(driver, pageTitles);
}
}
MainMenuPage.java
package com.lishman.qa.browser.pageobjects;
import static com.lishman.qa.browser.pageobjects.ApplicationPage.BLACK;
import static com.lishman.qa.browser.pageobjects.ApplicationPage.SEA_GREEN;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class MainMenuPage extends Page {
@FindBy(css = "h1")
private WebElement header;
@FindBy(id = "take-a-test")
private WebElement takeATest;
@FindBy(id = "quest-maint")
private WebElement questionMaintenance;
@FindBy(id = "tag-maint")
private WebElement tagMaintenance;
@FindBy(id = "theme-plain")
private WebElement plainTheme;
@FindBy(id = "theme-silver")
private WebElement silverTheme;
@FindBy(id = "locale-en")
private WebElement englishLocale;
@FindBy(id = "locale-fr")
private WebElement frenchLocale;
@FindBy(id = "locale-de")
private WebElement germanLocale;
public MainMenuPage (WebDriver driver) {
super(driver, new String[] {"Questions & Answers", "Questions et réponses", "Fragen und Antworten"});
}
public TakeATestPage takeATest() {
takeATest.click();
return PageFactory.initElements(getDriver(), TakeATestPage.class);
}
public QuestionListPage questionMaintenance() {
questionMaintenance.click();
return PageFactory.initElements(getDriver(), QuestionListPage.class);
}
public TagListPage tagMainteanance() {
tagMaintenance.click();
return PageFactory.initElements(getDriver(), TagListPage.class);
}
public String headerText() {
return header.getText();
}
public void selectPlainTheme() {
plainTheme.click();
waitForElementToBeColour(header, BLACK);
}
public void selectSilverTheme() {
silverTheme.click();
waitForElementToBeColour(header, SEA_GREEN);
}
public void selectEnglish() {
englishLocale.click();
waitForElementToContainText(header, "Answers");
}
public void selectFrench() {
frenchLocale.click();
waitForElementToContainText(header, "réponses");
}
public void selectGerman() {
germanLocale.click();
waitForElementToContainText(header, "Antworten");
}
public String headerColour() {
return header.getCssValue("color");
}
public String takeATestColour() {
return takeATest.getCssValue("color");
}
public String questionMaintenanceColour() {
return questionMaintenance.getCssValue("color");
}
public String tagMaintenanceColour() {
return tagMaintenance.getCssValue("color");
}
public String plainThemeColour() {
return plainTheme.getCssValue("color");
}
public String silverThemeColour() {
return silverTheme.getCssValue("color");
}
public String englishLocaleColour() {
return englishLocale.getCssValue("color");
}
public String frenchLocaleColour() {
return frenchLocale.getCssValue("color");
}
public String germanLocaleColour() {
return germanLocale.getCssValue("color");
}
}
Page.java
package com.lishman.qa.browser.pageobjects;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import com.google.common.base.Function;
public class Page {
private WebDriver driver;
public Page (WebDriver driver, String[] pageTitles) {
this.driver = driver;
waitForPageToLoad(pageTitles);
}
public WebDriver getDriver () {
return driver;
}
public String getTitle() {
return driver.getTitle();
}
public void clickOnLinkText(String linkText) {
driver.findElement(By.linkText(linkText)).click();
}
public String getPopupText() {
return driver.switchTo().alert().getText();
}
public void cancelConfirmation() {
driver.switchTo().alert().dismiss();
}
public void acceptConfirmation() {
driver.switchTo().alert().accept();
}
public void wait(int seconds) {
try {
Thread.sleep(seconds * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void waitForPageToLoad(final String[] pageTitles) {
fluentWait().until(new Function<WebDriver, Boolean>() {
public Boolean apply(WebDriver driver) {
for (int i = 0; i < pageTitles.length; i++) {
if (pageTitles[i].equals(driver.getTitle())) {
return true;
}
}
return false;
}
});
}
public void waitForElementToBeColour(final WebElement element, final String colour) {
fluentWait().until(new Function<WebDriver, Boolean>() {
public Boolean apply(WebDriver driver) {
return element.getCssValue("color").equals(colour);
}
});
}
public void waitForElementToContainText(final WebElement element, final String value) {
fluentWait().until(new Function<WebDriver, Boolean>() {
public Boolean apply(WebDriver driver) {
return StringUtils.contains(element.getText(), value);
}
});
}
@SuppressWarnings("unchecked")
private Wait<WebDriver> fluentWait() {
return new FluentWait<WebDriver>(driver)
.withTimeout(30, TimeUnit.SECONDS)
.pollingEvery(100, TimeUnit.MILLISECONDS)
.ignoring(NoSuchElementException.class);
}
}
QuestionDetailsPage.java
package com.lishman.qa.browser.pageobjects;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class QuestionDetailsPage extends Page {
@FindBy(css = "h1")
private WebElement header;
@FindBy(id = "question")
private WebElement question;
@FindBy(id = "answer")
private WebElement answer;
@FindBy(id = "tags")
private WebElement tags;
@FindBy(id = "go-back")
private WebElement backLink;
public QuestionDetailsPage(WebDriver driver) {
super(driver, new String[] {"Question Details"});
}
public String headerText() {
return header.getText();
}
public String getQuestion() {
return question.getText();
}
public String getTags() {
return tags.getText();
}
public String getAnswer() {
return answer.getText();
}
public QuestionListPage clickBackLink() {
backLink.click();
return PageFactory.initElements(getDriver(), QuestionListPage.class);
}
}
QuestionListPage.java
package com.lishman.qa.browser.pageobjects;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class QuestionListPage extends Page {
@FindBy(css = "h1")
private WebElement header;
@FindBy(id = "create")
private WebElement createButton;
@FindBy(css = "table#question tbody tr:nth-child(2) td:nth-child(1) a")
private WebElement editFirstQuestion;
@FindBy(css = "table#question tbody tr:nth-child(2) td:nth-child(2) a")
private WebElement firstQuestion;
@FindBy(css = "table#question tbody tr:nth-child(2) td:nth-child(3)")
private WebElement firstAnswer;
@FindBy(css = "table#question tbody tr:nth-child(3) td:nth-child(1) a")
private WebElement editSecondQuestion;
@FindBy(css = "table#question tbody tr:nth-child(3) td:nth-child(2) a")
private WebElement secondQuestion;
@FindBy(css = "table#question tbody tr:nth-child(3) td:nth-child(3)")
private WebElement secondAnswer;
@FindBy(id = "go-back")
private WebElement backLink;
public QuestionListPage(WebDriver driver) {
super(driver, new String[] {"Questions"});
}
public String headerText() {
return header.getText();
}
public QuestionMaintenancePage clickCreateButton() {
createButton.click();
return PageFactory.initElements(getDriver(), QuestionMaintenancePage.class);
}
public String getFirstQuestion() {
return firstQuestion.getText();
}
public String getFirstAnswer() {
return firstAnswer.getText();
}
public QuestionDetailsPage selectFirstQuestion() {
firstQuestion.click();
return PageFactory.initElements(getDriver(), QuestionDetailsPage.class);
}
public QuestionMaintenancePage editFirstQuestion() {
editFirstQuestion.click();
return PageFactory.initElements(getDriver(), QuestionMaintenancePage.class);
}
public String getSecondQuestion() {
return secondQuestion.getText();
}
public String getSecondAnswer() {
return secondAnswer.getText();
}
public QuestionDetailsPage selectSecondQuestion() {
secondQuestion.click();
return PageFactory.initElements(getDriver(), QuestionDetailsPage.class);
}
public QuestionMaintenancePage editSecondQuestion() {
editSecondQuestion.click();
return PageFactory.initElements(getDriver(), QuestionMaintenancePage.class);
}
public MainMenuPage clickBackLink() {
backLink.click();
return PageFactory.initElements(getDriver(), MainMenuPage.class);
}
}
QuestionMaintenancePage.java
package com.lishman.qa.browser.pageobjects;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class QuestionMaintenancePage extends Page {
@FindBy(css = "h1")
private WebElement header;
@FindBy(id = "create")
private WebElement createButton;
@FindBy(id = "save")
private WebElement saveButton;
@FindBy(id = "delete")
private WebElement deleteButton;
@FindBy(id = "question")
private WebElement questionField;
@FindBy(id = "question.errors")
private WebElement questionFieldErrors;
@FindBy(id = "answer")
private WebElement answerField;
@FindBy(id = "answer.errors")
private WebElement answerFieldErrors;
@FindBy(id = "tags1")
private WebElement firstTagCheckBox;
@FindBy(id = "tags2")
private WebElement secondTagCheckBox;
@FindBy(id = "cancel")
private WebElement cancel;
public QuestionMaintenancePage(WebDriver driver) {
super(driver, new String[] {"Question Maintenance"});
}
public String headerText() {
return header.getText();
}
public String getQuestion() {
return questionField.getText();
}
public void enterQuestion(String question) {
questionField.clear();
questionField.sendKeys(question);
}
public String getQuestionErrors () {
return questionFieldErrors.getText();
}
public String getAnswer() {
return answerField.getText();
}
public void enterAnswer(String answer) {
answerField.clear();
answerField.sendKeys(answer);
}
public String getAnswerErrors () {
return answerFieldErrors.getText();
}
public void clickFirstTag() {
firstTagCheckBox.click();
}
public void clickSecondTag() {
secondTagCheckBox.click();
}
public boolean isCreateButtonDisplayed() {
return createButton.isDisplayed();
}
public QuestionListPage clickCreateButton() {
createButton.click();
return PageFactory.initElements(getDriver(), QuestionListPage.class);
}
public void clickCreateButtonWithError() {
createButton.click();
}
public boolean isSaveButtonDisplayed() {
return saveButton.isDisplayed();
}
public QuestionListPage clickSaveButton() {
saveButton.click();
return PageFactory.initElements(getDriver(), QuestionListPage.class);
}
public void clickSaveButtonWithError() {
saveButton.click();
}
public boolean isDeleteButtonDisplayed() {
return deleteButton.isDisplayed();
}
public void clickDeleteButton() {
deleteButton.click();
}
public void cancelDelete() {
super.cancelConfirmation();
}
public QuestionListPage confirmDelete() {
super.acceptConfirmation();
return PageFactory.initElements(getDriver(), QuestionListPage.class);
}
public QuestionListPage clickCancel() {
cancel.click();
return PageFactory.initElements(getDriver(), QuestionListPage.class);
}
}
TagDetailsPage.java
package com.lishman.qa.browser.pageobjects;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class TagDetailsPage extends Page {
@FindBy(css = "h1")
private WebElement header;
@FindBy(css = "table#tag tbody tr:nth-child(1) td:nth-child(2)")
private WebElement tagName;
@FindBy(css = "table#tag tbody tr:nth-child(2) td:nth-child(2)")
private WebElement tagDescription;
@FindBy(id = "go-back")
private WebElement backLink;
public TagDetailsPage(WebDriver driver) {
super(driver, new String[] {"Tag Details"});
}
public String headerText() {
return header.getText();
}
public String getTagName() {
return tagName.getText();
}
public String getTagDescription() {
return tagDescription.getText();
}
public TagListPage clickBackLink() {
backLink.click();
return PageFactory.initElements(getDriver(), TagListPage.class);
}
}
TagListPage.java
package com.lishman.qa.browser.pageobjects;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class TagListPage extends Page {
@FindBy(css = "h1")
private WebElement header;
@FindBy(id = "create")
private WebElement createButton;
@FindBy(css = "table#tag tbody tr:nth-child(2) td:nth-child(1) a")
private WebElement editFirstTag;
@FindBy(css = "table#tag tbody tr:nth-child(2) td:nth-child(2) a")
private WebElement firstTagName;
@FindBy(css = "table#tag tbody tr:nth-child(2) td:nth-child(3)")
private WebElement firstTagDescription;
@FindBy(css = "table#tag tbody tr:nth-child(3) td:nth-child(1) a")
private WebElement editSecondTag;
@FindBy(css = "table#tag tbody tr:nth-child(3) td:nth-child(2) a")
private WebElement secondTagName;
@FindBy(css = "table#tag tbody tr:nth-child(3) td:nth-child(3)")
private WebElement secondTagDescription;
@FindBy(id = "go-back")
private WebElement backLink;
public TagListPage(WebDriver driver) {
super(driver, new String[] {"Tags"});
}
public String headerText() {
return header.getText();
}
public TagMaintenancePage clickCreateButton() {
createButton.click();
return PageFactory.initElements(getDriver(), TagMaintenancePage.class);
}
public String getFirstTagName() {
return firstTagName.getText();
}
public String getFirstTagDescription() {
return firstTagDescription.getText();
}
public TagDetailsPage selectFirstTag() {
firstTagName.click();
return PageFactory.initElements(getDriver(), TagDetailsPage.class);
}
public TagMaintenancePage editFirstTag() {
editFirstTag.click();
return PageFactory.initElements(getDriver(), TagMaintenancePage.class);
}
public String getSecondTagName() {
return secondTagName.getText();
}
public String getSecondTagDescription() {
return secondTagDescription.getText();
}
public TagDetailsPage selectSecondTag() {
secondTagName.click();
return PageFactory.initElements(getDriver(), TagDetailsPage.class);
}
public TagMaintenancePage editSecondTag() {
editSecondTag.click();
return PageFactory.initElements(getDriver(), TagMaintenancePage.class);
}
public MainMenuPage clickBackLink() {
backLink.click();
return PageFactory.initElements(getDriver(), MainMenuPage.class);
}
}
TagMaintenancePage.java
package com.lishman.qa.browser.pageobjects;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class TagMaintenancePage extends Page {
@FindBy(css = "h1")
private WebElement header;
@FindBy(id = "create")
private WebElement createButton;
@FindBy(id = "save")
private WebElement saveButton;
@FindBy(id = "delete")
private WebElement deleteButton;
@FindBy(id = "name")
private WebElement nameField;
@FindBy(id = "name.errors")
private WebElement nameFieldErrors;
@FindBy(id = "description")
private WebElement descriptionField;
@FindBy(id = "cancel")
private WebElement cancel;
public TagMaintenancePage(WebDriver driver) {
super(driver, new String[] {"Tag Maintenance"});
}
public String headerText() {
return header.getText();
}
public String getName() {
return nameField.getAttribute("value");
}
public void enterName(String name) {
nameField.clear();
nameField.sendKeys(name);
}
public String getNameErrors () {
return nameFieldErrors.getText();
}
public String getDescription() {
return descriptionField.getAttribute("value");
}
public void enterDescription(String description) {
descriptionField.clear();
descriptionField.sendKeys(description);
}
public boolean isCreateButtonDisplayed() {
return createButton.isDisplayed();
}
public TagListPage clickCreateButton() {
createButton.click();
return PageFactory.initElements(getDriver(), TagListPage.class);
}
public void clickCreateButtonWithError() {
createButton.click();
}
public boolean isSaveButtonDisplayed() {
return saveButton.isDisplayed();
}
public TagListPage clickSaveButton() {
saveButton.click();
return PageFactory.initElements(getDriver(), TagListPage.class);
}
public void clickSaveButtonWithError() {
saveButton.click();
}
public boolean isDeleteButtonDisplayed() {
return deleteButton.isDisplayed();
}
public void clickDeleteButton() {
deleteButton.click();
}
public void cancelDelete() {
super.cancelConfirmation();
}
public TagListPage confirmDelete() {
super.acceptConfirmation();
return PageFactory.initElements(getDriver(), TagListPage.class);
}
public TagListPage clickCancel() {
cancel.click();
return PageFactory.initElements(getDriver(), TagListPage.class);
}
}
TakeATestPage.java
package com.lishman.qa.browser.pageobjects;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class TakeATestPage extends Page {
@FindBy(css = "h2")
private WebElement tags;
@FindBy(id = "reveal")
private WebElement revealButton;
@FindBy(id = "question")
private WebElement questionField;
@FindBy(id = "answer")
private WebElement answerField;
@FindBy(id = "menu")
private WebElement menu;
@FindBy(id = "next-quest")
private WebElement nextQuestion;
public TakeATestPage(WebDriver driver) {
super(driver, new String[] {"Take a Test"});
}
public String getTags() {
return tags.getText();
}
public String getQuestion() {
return questionField.getText();
}
public boolean isAnswerDisplayed() {
return answerField.isDisplayed();
}
public String getAnswer() {
return answerField.getText();
}
public void enterAnswer(String answer) {
answerField.clear();
answerField.sendKeys(answer);
}
public void revealAnswer() {
revealButton.click();
}
public MainMenuPage clickMenu() {
menu.click();
return PageFactory.initElements(getDriver(), MainMenuPage.class);
}
public void clickNextQuestion() {
nextQuestion.click();
}
public MainMenuPage clickNextQuestionWithNoMoreQuestions() {
nextQuestion.click();
return PageFactory.initElements(getDriver(), MainMenuPage.class);
}
}
browser-test.properties
app.host=http://localhost:8080
BrowserTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<util:properties id="browserTestProperties"
location="classpath:com/lishman/qa/browser/browser-test.properties"/>
</beans>
BrowserTest.java
package com.lishman.qa.browser;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.client.RestTemplate;
import com.lishman.qa.TestExt;
import com.lishman.qa.domain.Question;
import com.lishman.qa.domain.Tag;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class BrowserTest extends TestExt {
private static WebDriver driver;
private static boolean manageDriverHere = true;
private String testHost;
@BeforeClass
public static void classSetUp() {
if (manageDriverHere) {
driver = new FirefoxDriver();
}
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
}
@AfterClass
public static void classTearDown() {
if (manageDriverHere) {
driver.quit();
}
}
public static WebDriver getDriver() {
return driver;
}
public static void setDriver(WebDriver webDriver) {
manageDriverHere = false;
driver = webDriver;
}
public String getTestHost() {
return testHost;
}
@Value("#{browserTestProperties['app.host']}")
public void setTestHost (String testHost) {
this.testHost = testHost;
}
public void resetQaServiceStub() {
RestTemplate restTemplate = new RestTemplate();
String url = getTestHost() + "/qa1/app/reset";
restTemplate.postForObject(url, null, String.class);
}
public Tag getTagByNameFromStub(String tagName) {
RestTemplate restTemplate = new RestTemplate();
String url = getTestHost() + "/qa1/app/get-tag/" + tagName;
return restTemplate.getForObject(url, Tag.class);
}
@SuppressWarnings("unchecked")
public List<LinkedHashMap<String, String>> getAllTagsFromStub() {
RestTemplate restTemplate = new RestTemplate();
String url = getTestHost() + "/qa1/app/get-tags";
List<LinkedHashMap<String, String>> tags = restTemplate.getForObject(url, List.class);
return tags;
}
public Question getQuestionByTextFromStub(String questionName) {
RestTemplate restTemplate = new RestTemplate();
String url = getTestHost() + "/qa1/app/get-question/" + questionName;
return restTemplate.getForObject(url, Question.class);
}
@SuppressWarnings("unchecked")
public List<LinkedHashMap<String, String>> getAllQuestionsFromStub() {
RestTemplate restTemplate = new RestTemplate();
String url = getTestHost() + "/qa1/app/get-questions";
List<LinkedHashMap<String, String>> tags = restTemplate.getForObject(url, List.class);
return tags;
}
}
MainMenuTest.java
package com.lishman.qa.browser;
import static com.lishman.qa.browser.pageobjects.ApplicationPage.BLACK;
import static com.lishman.qa.browser.pageobjects.ApplicationPage.BLUE;
import static com.lishman.qa.browser.pageobjects.ApplicationPage.DARK_SLATE_GRAY;
import static com.lishman.qa.browser.pageobjects.ApplicationPage.SEA_GREEN;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.support.PageFactory;
import com.lishman.qa.browser.pageobjects.MainMenuPage;
public class MainMenuTest extends BrowserTest {
private MainMenuPage mainMenuPage;
@Before
public void setUp() {
getDriver().get(getTestHost() + "/qa1/app/");
mainMenuPage = PageFactory.initElements(getDriver(), MainMenuPage.class);
mainMenuPage.selectEnglish();
}
@Test
public void selectingPlainThemeDisplaysPageInPlainMode() {
mainMenuPage.selectPlainTheme();
assertThat(mainMenuPage.headerColour(), equalTo(BLACK));
testMainMenuColours(BLUE);
}
@Test
public void selectingSilverThemeDisplaysPageInSilverMode() {
mainMenuPage.selectSilverTheme();
assertThat(mainMenuPage.headerColour(), equalTo(SEA_GREEN));
testMainMenuColours(DARK_SLATE_GRAY);
}
@Test
public void selectingEnglishLanguageDisplaysPageInEnglish() {
mainMenuPage.selectEnglish();
assertThat(mainMenuPage.headerText(), equalTo("Questions & Answers"));
}
@Test
public void selectingFrenchLanguageDisplaysPageInFrench() {
mainMenuPage.selectFrench();
assertThat(mainMenuPage.headerText(), equalTo("Questions et réponses"));
}
@Test
public void selectingGermanLanguageDisplaysPageInGerman() {
mainMenuPage.selectGerman();
assertThat(mainMenuPage.headerText(), equalTo("Fragen und Antworten"));
}
@Test
public void clickingTakeATestOptionNavigatesToTestPage() {
mainMenuPage.takeATest();
assertThat(mainMenuPage.getTitle(), equalTo("Take a Test"));
}
@Test
public void clickingQuestionMaintenanceOptionNavigatesToQuestionMaintenancePage() {
mainMenuPage.questionMaintenance();
assertThat(mainMenuPage.getTitle(), equalTo("Questions"));
}
@Test
public void clickingTagMaintenanceOptionNavigatesToTagMaintenancePage() {
mainMenuPage.tagMainteanance();
assertThat(mainMenuPage.getTitle(), equalTo("Tags"));
}
private void testMainMenuColours(String colour) {
assertThat(mainMenuPage.takeATestColour(), equalTo(colour));
assertThat(mainMenuPage.questionMaintenanceColour(), equalTo(colour));
assertThat(mainMenuPage.tagMaintenanceColour(), equalTo(colour));
assertThat(mainMenuPage.plainThemeColour(), equalTo(colour));
assertThat(mainMenuPage.silverThemeColour(), equalTo(colour));
assertThat(mainMenuPage.englishLocaleColour(), equalTo(colour));
assertThat(mainMenuPage.frenchLocaleColour(), equalTo(colour));
assertThat(mainMenuPage.germanLocaleColour(), equalTo(colour));
}
}
QuestionDetailsTest.java
package com.lishman.qa.browser;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.support.PageFactory;
import com.lishman.qa.browser.pageobjects.QuestionDetailsPage;
import com.lishman.qa.browser.pageobjects.QuestionListPage;
public class QuestionDetailsTest extends BrowserTest {
private QuestionDetailsPage questionDetailsPage;
@Before
public void setUp() {
getDriver().get(getTestHost() + "/qa1/app/question-details/1");
questionDetailsPage = PageFactory.initElements(getDriver(), QuestionDetailsPage.class);
}
@Test
public void correctHeaderIsDisplayedWhenPageIsLoaded() {
assertThat(questionDetailsPage.headerText(), equalTo("Question Details"));
}
@Test
public void correctQuestionDetailsAreDisplayedForSpecifiedIdentifier() {
assertThat(questionDetailsPage.getQuestion(), equalTo("question one"));
assertThat(questionDetailsPage.getAnswer(), equalTo("answer one"));
assertThat(questionDetailsPage.getTags(), equalTo("Tags: one"));
getDriver().get(getTestHost() + "/qa1/app/question-details/2");
questionDetailsPage = PageFactory.initElements(getDriver(), QuestionDetailsPage.class);
assertThat(questionDetailsPage.getQuestion(), equalTo("question two"));
assertThat(questionDetailsPage.getAnswer(), equalTo("answer two"));
assertThat(questionDetailsPage.getTags(), equalTo("Tags: one, two"));
}
@Test
public void questionListPageIsDisplayedWhenBackLinkIsClicked() {
QuestionListPage questionListPage = questionDetailsPage.clickBackLink();
assertThat(questionListPage.getTitle(), equalTo("Questions"));
}
}
QuestionListTest.java
package com.lishman.qa.browser;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.support.PageFactory;
import com.lishman.qa.browser.pageobjects.MainMenuPage;
import com.lishman.qa.browser.pageobjects.QuestionDetailsPage;
import com.lishman.qa.browser.pageobjects.QuestionListPage;
import com.lishman.qa.browser.pageobjects.QuestionMaintenancePage;
public class QuestionListTest extends BrowserTest {
private QuestionListPage questionListPage;
@Before
public void setUp() {
getDriver().get(getTestHost() + "/qa1/app/question-list.html");
questionListPage = PageFactory.initElements(getDriver(), QuestionListPage.class);
}
@Test
public void correctHeaderIsDisplayedWhenPageIsLoaded() {
assertThat(questionListPage.headerText(), equalTo("Questions"));
}
@Test
public void allQuestionsAreDisplayedInTheTable() {
assertThat(questionListPage.getFirstQuestion(), equalTo("question one"));
assertThat(questionListPage.getFirstAnswer(), equalTo("answer one"));
assertThat(questionListPage.getSecondQuestion(), equalTo("question two"));
assertThat(questionListPage.getSecondAnswer(), equalTo("answer two"));
}
@Test
public void firstQuestionDetailsAreDisplayedWhenFirstQuestionNameIsClicked() {
QuestionDetailsPage questionDetailsPage = questionListPage.selectFirstQuestion();
assertThat(questionDetailsPage.getTitle(), equalTo("Question Details"));
assertThat(questionDetailsPage.getQuestion(), equalTo("question one"));
}
@Test
public void secondQuestionDetailsAreDisplayedWhenSecondQuestionNameIsClicked() {
QuestionDetailsPage questionDetailsPage = questionListPage.selectSecondQuestion();
assertThat(questionDetailsPage.getTitle(), equalTo("Question Details"));
assertThat(questionDetailsPage.getQuestion(), equalTo("question two"));
}
@Test
public void firstQuestionCanBeEditedWhenFirstEditButtonIsClicked() {
QuestionMaintenancePage questionMaintenancePage = questionListPage.editFirstQuestion();
assertThat(questionMaintenancePage.getTitle(), equalTo("Question Maintenance"));
assertThat(questionMaintenancePage.getQuestion(), equalTo("question one"));
}
@Test
public void secondQuestionCanBeEditedWhenSecondEditButtonIsClicked() {
QuestionMaintenancePage questionMaintenancePage = questionListPage.editSecondQuestion();
assertThat(questionMaintenancePage.getTitle(), equalTo("Question Maintenance"));
assertThat(questionMaintenancePage.getQuestion(), equalTo("question two"));
}
@Test
public void createQuestionPageIsDisplayedWhenCreateButtonIsClicked() {
QuestionMaintenancePage questionMaintenancePage = questionListPage.clickCreateButton();
assertThat(questionMaintenancePage.getTitle(), equalTo("Question Maintenance"));
assertThat(questionMaintenancePage.getQuestion(), equalTo(""));
}
@Test
public void mainMenuPageIsDisplayedWhenBackLinkIsClicked() {
MainMenuPage mainMenuPage = questionListPage.clickBackLink();
assertThat(mainMenuPage.getTitle(), equalTo("Questions & Answers"));
}
}
QuestionMaintenanceTest.java
package com.lishman.qa.browser;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.support.PageFactory;
import com.lishman.qa.browser.pageobjects.QuestionListPage;
import com.lishman.qa.browser.pageobjects.QuestionMaintenancePage;
import com.lishman.qa.domain.Question;
import com.lishman.qa.domain.Tag;
public class QuestionMaintenanceTest extends BrowserTest {
QuestionMaintenancePage questionMaintenancePage;
@Before
public void setUp() {
getDriver().get(getTestHost() + "/qa1/app/question-form.html");
questionMaintenancePage = PageFactory.initElements(getDriver(), QuestionMaintenancePage.class);
}
@After
public void tearDown() {
super.resetQaServiceStub();
}
@Test
public void correctHeaderIsDisplayedWhenPageIsLoaded() {
assertThat(questionMaintenancePage.headerText(), equalTo("Question Maintenance"));
}
@Test
public void questionIsCreatedWhenValidDetailsAreEnteredAndCreateButtonIsClicked() {
questionMaintenancePage.enterQuestion("my-question");
questionMaintenancePage.enterAnswer("my-desc");
QuestionListPage questionListPage = questionMaintenancePage.clickCreateButton();
assertThat(questionListPage.getTitle(), equalTo("Questions"));
Question question = super.getQuestionByTextFromStub("my-question");
assertThat(question.getQuestion(), equalTo("my-question"));
assertThat(question.getAnswer(), equalTo("my-desc"));
}
@Test
public void questionIsCreatedWithOneTag() {
questionMaintenancePage.enterQuestion("my-question");
questionMaintenancePage.enterAnswer("my-desc");
questionMaintenancePage.clickFirstTag();
QuestionListPage questionListPage = questionMaintenancePage.clickCreateButton();
assertThat(questionListPage.getTitle(), equalTo("Questions"));
Question question = super.getQuestionByTextFromStub("my-question");
assertThat(question.getQuestion(), equalTo("my-question"));
assertThat(question.getAnswer(), equalTo("my-desc"));
assertThat(question.getTags().iterator().next().getName(), equalTo("one"));
}
@Test
public void questionIsCreatedWithTwoTags() {
questionMaintenancePage.enterQuestion("my-question");
questionMaintenancePage.enterAnswer("my-desc");
questionMaintenancePage.clickFirstTag();
questionMaintenancePage.clickSecondTag();
QuestionListPage questionListPage = questionMaintenancePage.clickCreateButton();
assertThat(questionListPage.getTitle(), equalTo("Questions"));
Question question = super.getQuestionByTextFromStub("my-question");
assertThat(question.getQuestion(), equalTo("my-question"));
assertThat(question.getAnswer(), equalTo("my-desc"));
Iterator<Tag> iter = question.getTags().iterator();
assertThat(iter.next().getName(), equalTo("one"));
assertThat(iter.next().getName(), equalTo("two"));
}
@Test
public void newQuestionWithNoQuestionAndAnswerIsRejected() {
questionMaintenancePage.clickCreateButtonWithError();
assertThat(questionMaintenancePage.getQuestionErrors(), equalTo("must not be empty"));
assertThat(questionMaintenancePage.getAnswerErrors(), equalTo("must not be empty"));
List<LinkedHashMap<String, String>> questions = super.getAllQuestionsFromStub();
assertThat(questions.size(), equalTo(2));
}
@Test
public void questionIsUpdatedWhenDetailsAreChangedAndUpdateButtonIsClicked() {
getDriver().get(getTestHost() + "/qa1/app/question-form.html?id=1");
questionMaintenancePage = PageFactory.initElements(getDriver(), QuestionMaintenancePage.class);
questionMaintenancePage.enterQuestion("my-question");
questionMaintenancePage.enterAnswer("my-answer");
questionMaintenancePage.clickFirstTag();
questionMaintenancePage.clickSecondTag();
QuestionListPage questionListPage = questionMaintenancePage.clickSaveButton();
assertThat(questionListPage.getTitle(), equalTo("Questions"));
Question question = getQuestionByTextFromStub("my-question");
assertThat(question.getQuestion(), equalTo("my-question"));
assertThat(question.getAnswer(), equalTo("my-answer"));
assertThat(question.getTags().iterator().next().getName(), equalTo("two"));
}
@Test
public void existingQuestionWithNoQuestionOrAnswerIsRejected() {
getDriver().get(getTestHost() + "/qa1/app/question-form.html?id=1");
questionMaintenancePage = PageFactory.initElements(getDriver(), QuestionMaintenancePage.class);
questionMaintenancePage.enterQuestion("");
questionMaintenancePage.enterAnswer("");
questionMaintenancePage.clickSaveButtonWithError();
assertThat(questionMaintenancePage.getQuestionErrors(), equalTo("must not be empty"));
assertThat(questionMaintenancePage.getAnswerErrors(), equalTo("must not be empty"));
List<LinkedHashMap<String, String>> questions = super.getAllQuestionsFromStub();
assertThat(questions.size(), equalTo(2));
}
@Test
public void questionIsDeletedWhenDeleteButtonIsClicked() {
getDriver().get(getTestHost() + "/qa1/app/question-form.html?id=1");
questionMaintenancePage = PageFactory.initElements(getDriver(), QuestionMaintenancePage.class);
questionMaintenancePage.clickDeleteButton();
assertThat(questionMaintenancePage.getPopupText(), equalTo("Delete Question?"));
QuestionListPage questionListPage = questionMaintenancePage.confirmDelete();
assertThat(questionListPage.getTitle(), equalTo("Questions"));
List<LinkedHashMap<String, String>> questions = super.getAllQuestionsFromStub();
assertThat(questions.size(), equalTo(1));
Question question = getQuestionByTextFromStub("question two");
assertThat(question.getQuestion(), equalTo("question two"));
}
@Test
public void cancellingQuestionMaintenanceReturnsToListPage() {
QuestionListPage questionListPage = questionMaintenancePage.clickCancel();
assertThat(questionListPage.getTitle(), equalTo("Questions"));
List<LinkedHashMap<String, String>> questions = super.getAllQuestionsFromStub();
assertThat(questions.size(), equalTo(2));
assertThat(questions.get(0).get("question"), equalTo("question one"));
assertThat(questions.get(1).get("question"), equalTo("question two"));
}
}
TagDetailsTest.java
package com.lishman.qa.browser;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.support.PageFactory;
import com.lishman.qa.browser.pageobjects.TagDetailsPage;
import com.lishman.qa.browser.pageobjects.TagListPage;
public class TagDetailsTest extends BrowserTest {
private TagDetailsPage tagDetailsPage;
@Before
public void setUp() {
getDriver().get(getTestHost() + "/qa1/app/tag-details/1");
tagDetailsPage = PageFactory.initElements(getDriver(), TagDetailsPage.class);
}
@Test
public void correctHeaderIsDisplayedWhenPageIsLoaded() {
assertThat(tagDetailsPage.headerText(), equalTo("Tag Details"));
}
@Test
public void correctTagDetailsAreDisplayedForSpecifiedIdentifier() {
assertThat(tagDetailsPage.getTagName(), equalTo("one"));
assertThat(tagDetailsPage.getTagDescription(), equalTo("description one"));
getDriver().get(getTestHost() + "/qa1/app/tag-details/2");
tagDetailsPage = PageFactory.initElements(getDriver(), TagDetailsPage.class);
assertThat(tagDetailsPage.getTagName(), equalTo("two"));
assertThat(tagDetailsPage.getTagDescription(), equalTo("description two"));
}
@Test
public void tagListPageIsDisplayedWhenBackLinkIsClicked() {
TagListPage tagListPage = tagDetailsPage.clickBackLink();
assertThat(tagListPage.getTitle(), equalTo("Tags"));
}
}
TagListTest.java
package com.lishman.qa.browser;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.support.PageFactory;
import com.lishman.qa.browser.pageobjects.MainMenuPage;
import com.lishman.qa.browser.pageobjects.TagDetailsPage;
import com.lishman.qa.browser.pageobjects.TagListPage;
import com.lishman.qa.browser.pageobjects.TagMaintenancePage;
public class TagListTest extends BrowserTest {
private TagListPage tagListPage;
@Before
public void setUp() {
getDriver().get(getTestHost() + "/qa1/app/tag-list.html");
tagListPage = PageFactory.initElements(getDriver(), TagListPage.class);
}
@Test
public void correctHeaderIsDisplayedWhenPageIsLoaded() {
assertThat(tagListPage.headerText(), equalTo("Tags"));
}
@Test
public void allTagsAreDisplayedInTheTable() {
assertThat(tagListPage.getFirstTagName(), equalTo("one"));
assertThat(tagListPage.getFirstTagDescription(), equalTo("description one"));
assertThat(tagListPage.getSecondTagName(), equalTo("two"));
assertThat(tagListPage.getSecondTagDescription(), equalTo("description two"));
}
@Test
public void firstTagDetailsAreDisplayedWhenFirstTagNameIsClicked() {
TagDetailsPage tagDetailsPage = tagListPage.selectFirstTag();
assertThat(tagDetailsPage.getTitle(), equalTo("Tag Details"));
assertThat(tagDetailsPage.getTagName(), equalTo("one"));
}
@Test
public void secondTagDetailsAreDisplayedWhenSecondTagNameIsClicked() {
TagDetailsPage tagDetailsPage = tagListPage.selectSecondTag();
assertThat(tagDetailsPage.getTitle(), equalTo("Tag Details"));
assertThat(tagDetailsPage.getTagName(), equalTo("two"));
}
@Test
public void firstTagCanBeEditedWhenFirstEditButtonIsClicked() {
TagMaintenancePage tagMaintenancePage = tagListPage.editFirstTag();
assertThat(tagMaintenancePage.getTitle(), equalTo("Tag Maintenance"));
assertThat(tagMaintenancePage.getName(), equalTo("one"));
}
@Test
public void secondTagCanBeEditedWhenSecondEditButtonIsClicked() {
TagMaintenancePage tagMaintenancePage = tagListPage.editSecondTag();
assertThat(tagMaintenancePage.getTitle(), equalTo("Tag Maintenance"));
assertThat(tagMaintenancePage.getName(), equalTo("two"));
}
@Test
public void createTagPageIsDisplayedWhenCreateButtonIsClicked() {
TagMaintenancePage tagMaintenancePage = tagListPage.clickCreateButton();
assertThat(tagMaintenancePage.getTitle(), equalTo("Tag Maintenance"));
assertThat(tagMaintenancePage.getName(), equalTo(""));
}
@Test
public void mainMenuPageIsDisplayedWhenBackLinkIsClicked() {
MainMenuPage mainMenuPage = tagListPage.clickBackLink();
assertThat(mainMenuPage.getTitle(), equalTo("Questions & Answers"));
}
}
TagMaintenanceTest.java
package com.lishman.qa.browser;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import java.util.LinkedHashMap;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.support.PageFactory;
import com.lishman.qa.browser.pageobjects.TagListPage;
import com.lishman.qa.browser.pageobjects.TagMaintenancePage;
import com.lishman.qa.domain.Tag;
public class TagMaintenanceTest extends BrowserTest {
TagMaintenancePage tagMaintenancePage;
@Before
public void setUp() {
getDriver().get(getTestHost() + "/qa1/app/tag-form.html");
tagMaintenancePage = PageFactory.initElements(getDriver(), TagMaintenancePage.class);
}
@After
public void tearDown() {
super.resetQaServiceStub();
}
@Test
public void correctHeaderIsDisplayedWhenPageIsLoaded() {
assertThat(tagMaintenancePage.headerText(), equalTo("Tag Maintenance"));
}
@Test
public void tagIsCreatedWhenValidNameIsEnteredAndCreateButtonIsClicked() {
tagMaintenancePage.enterName("my-tag");
tagMaintenancePage.enterDescription("my-desc");
TagListPage tagListPage = tagMaintenancePage.clickCreateButton();
assertThat(tagListPage.getTitle(), equalTo("Tags"));
Tag tag = super.getTagByNameFromStub("my-tag");
assertThat(tag.getName(), equalTo("my-tag"));
assertThat(tag.getDescription(), equalTo("my-desc"));
}
@Test
public void newTagWithNoNameIsRejected() {
tagMaintenancePage.clickCreateButtonWithError();
assertThat(tagMaintenancePage.getNameErrors(), equalTo("must not be empty"));
List<LinkedHashMap<String, String>> tags = super.getAllTagsFromStub();
assertThat(tags.size(), equalTo(2));
}
@Test
public void newTagIsRejectedIfTagNameAlreadyExists() {
tagMaintenancePage.enterName("one");
tagMaintenancePage.enterDescription("my-desc");
tagMaintenancePage.clickCreateButtonWithError();
assertThat(tagMaintenancePage.getNameErrors(), equalTo("already exists"));
List<LinkedHashMap<String, String>> tags = super.getAllTagsFromStub();
assertThat(tags.size(), equalTo(2));
}
@Test
public void tagIsUpdatedWhenNameIsChangedAndUpdateButtonIsClicked() {
getDriver().get(getTestHost() + "/qa1/app/tag-form.html?id=1");
tagMaintenancePage = PageFactory.initElements(getDriver(), TagMaintenancePage.class);
tagMaintenancePage.enterName("my-tag");
tagMaintenancePage.enterDescription("my-desc");
TagListPage tagListPage = tagMaintenancePage.clickSaveButton();
assertThat(tagListPage.getTitle(), equalTo("Tags"));
Tag tag = getTagByNameFromStub("my-tag");
assertThat(tag.getName(), equalTo("my-tag"));
assertThat(tag.getDescription(), equalTo("my-desc"));
}
@Test
public void existingTagWithNoNameIsRejected() {
getDriver().get(getTestHost() + "/qa1/app/tag-form.html?id=1");
tagMaintenancePage = PageFactory.initElements(getDriver(), TagMaintenancePage.class);
tagMaintenancePage.enterName("");
tagMaintenancePage.clickSaveButtonWithError();
assertThat(tagMaintenancePage.getNameErrors(), equalTo("must not be empty"));
List<LinkedHashMap<String, String>> tags = super.getAllTagsFromStub();
assertThat(tags.size(), equalTo(2));
}
@Test
public void existingTagIsRejectedIfTagNameAlreadyExists() {
getDriver().get(getTestHost() + "/qa1/app/tag-form.html?id=1");
tagMaintenancePage = PageFactory.initElements(getDriver(), TagMaintenancePage.class);
tagMaintenancePage.enterName("two");
tagMaintenancePage.clickSaveButtonWithError();
assertThat(tagMaintenancePage.getNameErrors(), equalTo("already exists"));
List<LinkedHashMap<String, String>> tags = super.getAllTagsFromStub();
assertThat(tags.size(), equalTo(2));
}
@Test
public void tagIsDeletedWhenDeleteButtonIsClicked() {
getDriver().get(getTestHost() + "/qa1/app/tag-form.html?id=1");
tagMaintenancePage = PageFactory.initElements(getDriver(), TagMaintenancePage.class);
tagMaintenancePage.clickDeleteButton();
assertThat(tagMaintenancePage.getPopupText(), equalTo("Delete Tag?"));
TagListPage tagListPage = tagMaintenancePage.confirmDelete();
assertThat(tagListPage.getTitle(), equalTo("Tags"));
List<LinkedHashMap<String, String>> tags = super.getAllTagsFromStub();
assertThat(tags.size(), equalTo(1));
Tag tag = getTagByNameFromStub("two");
assertThat(tag.getName(), equalTo("two"));
}
@Test
public void cancellingTagMaintenanceReturnsToListPage() {
TagListPage tagListPage = tagMaintenancePage.clickCancel();
assertThat(tagListPage.getTitle(), equalTo("Tags"));
List<LinkedHashMap<String, String>> tags = super.getAllTagsFromStub();
assertThat(tags.size(), equalTo(2));
assertThat(tags.get(0).get("name"), equalTo("one"));
assertThat(tags.get(1).get("name"), equalTo("two"));
}
}
TakeATestTest.java
package com.lishman.qa.browser;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.support.PageFactory;
import com.lishman.qa.browser.pageobjects.MainMenuPage;
import com.lishman.qa.browser.pageobjects.TakeATestPage;
public class TakeATestTest extends BrowserTest {
TakeATestPage takeATestPage;
@Before
public void setUp() {
getDriver().get(getTestHost() + "/qa1/app/ask-question.html");
takeATestPage = PageFactory.initElements(getDriver(), TakeATestPage.class);
}
@Test
public void testIsTakenFromStartToFinish() {
assertThat(takeATestPage.getTags(), equalTo("Tags: one"));
assertThat(takeATestPage.getQuestion(), equalTo("question one"));
assertThat(takeATestPage.isAnswerDisplayed(), equalTo(false));
takeATestPage.revealAnswer();
assertThat(takeATestPage.isAnswerDisplayed(), equalTo(true));
assertThat(takeATestPage.getAnswer(), equalTo("answer one"));
takeATestPage.clickNextQuestion();
assertThat(takeATestPage.getTags(), equalTo("Tags: one, two"));
assertThat(takeATestPage.getQuestion(), equalTo("question two"));
assertThat(takeATestPage.isAnswerDisplayed(), equalTo(false));
takeATestPage.revealAnswer();
assertThat(takeATestPage.isAnswerDisplayed(), equalTo(true));
assertThat(takeATestPage.getAnswer(), equalTo("answer two"));
MainMenuPage mainMenuPage = takeATestPage.clickNextQuestionWithNoMoreQuestions();
assertThat(mainMenuPage.getTitle(), equalTo("Questions & Answers"));
}
@Test
public void clickingMenuLinkReturnsToMenuPage() {
MainMenuPage mainMenuPage = takeATestPage.clickMenu();
assertThat(mainMenuPage.getTitle(), equalTo("Questions & Answers"));
}
}
JpaDaoTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<import resource="classpath:com/lishman/qa/domain/DomainTest-context.xml"/>
<import resource="classpath:data/jpa-config.xml"/>
</beans>
JpaQuestionDaoTest.java
package com.lishman.qa.data.jpa;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.util.List;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import com.lishman.qa.data.DatabaseTest;
import com.lishman.qa.data.QuestionDao;
import com.lishman.qa.domain.Question;
import com.lishman.qa.domain.Tag;
@ContextConfiguration("JpaDaoTest-context.xml")
public class JpaQuestionDaoTest extends DatabaseTest {
@Autowired
private QuestionDao questionDao;
@Autowired
private JpaTemplate jpaTemplate;
@Test
public void correctNumberOfRowsAreReturned() {
assertEquals(super.countRowsInTable("QUESTION"), questionDao.getAll().size());
}
@Test
public void questionIsReturnedForSpecifiedId() {
Question question = questionDao.getById(1);
assertEquals((Integer) 1, question.getId());
assertEquals("First question?", question.getQuestion());
assertEquals("Answer to first question", question.getAnswer());
}
@Test
public void tagsAreLinkedToQuestions() {
assertEquals(1, questionDao.getById(1).getTags().size());
assertEquals(2, questionDao.getById(2).getTags().size());
assertEquals(3, questionDao.getById(3).getTags().size());
}
@Test
public void questionIsCreatedWithTags() {
Question question = new Question("New question", "New answer");
question.getTags().add(jpaTemplate.find(Tag.class, 2));
question.getTags().add(jpaTemplate.find(Tag.class, 3));
Errors errors = new BeanPropertyBindingResult(question, "question");
Question savedQuestion = questionDao.save(question, errors);
jpaTemplate.flush();
assertEquals(0, errors.getAllErrors().size());
Integer currId = super.getCurrentSequenceValue("QUESTION_SEQ1");
currId *= 50; assertEquals(currId, savedQuestion.getId());
Question newQuestion = super.getQuestionById(currId);
assertEquals(currId, newQuestion.getId());
assertEquals("New question", newQuestion.getQuestion());
assertEquals("New answer" , newQuestion.getAnswer());
List<Tag> tags = super.getTagsForQuestion(currId);
assertEquals(2, tags.size());
assertEquals("Second Tag", tags.get(0).getName());
assertEquals("Third Tag", tags.get(1).getName());
}
@Test
public void invalidQuestionIsRejected() {
Question question = new Question(null, null);
Errors errors = new BeanPropertyBindingResult(question, "question");
Question savedQuestion = questionDao.save(question, errors);
jpaTemplate.flush();
assertNull(savedQuestion);
assertEquals(2, errors.getAllErrors().size());
assertEquals("NotBlank", errors.getFieldError("question").getCode());
assertEquals("NotBlank", errors.getFieldError("answer").getCode());
}
@Test(expected=InvalidDataAccessApiUsageException.class)
public void transientTagIsRejected() {
Question question = new Question("New question", "New answer");
question.getTags().add(new Tag("Transient", "Transient Object"));
Errors errors = new BeanPropertyBindingResult(question, "question");
questionDao.save(question, errors);
jpaTemplate.flush();
}
@Test
public void questionIsUpdatedWithNewTags() {
Question question = questionDao.getById(1);
assertEquals((Integer) 1, question.getId());
assertEquals("First question?", question.getQuestion());
assertEquals("Answer to first question", question.getAnswer());
Errors errors = new BeanPropertyBindingResult(question, "question");
question.setQuestion("Updated Question");
question.setAnswer("Updated Answer");
question.getTags().clear();
question.getTags().add(jpaTemplate.find(Tag.class, 2));
question.getTags().add(jpaTemplate.find(Tag.class, 3));
questionDao.save(question, errors);
jpaTemplate.flush();
assertEquals(0, errors.getAllErrors().size());
Question questionFromDb = super.getQuestionById(1);
assertEquals((Integer) 1, questionFromDb.getId());
assertEquals("Updated Question", questionFromDb.getQuestion());
assertEquals("Updated Answer", questionFromDb.getAnswer());
List<Tag> tags = super.getTagsForQuestion(1);
assertEquals(2, tags.size());
assertEquals("Second Tag", tags.get(0).getName());
assertEquals("Third Tag", tags.get(1).getName());
}
@Test
public void questionWithoutTagsIsDeleted() {
Question question = questionDao.getById(4);
assertNotNull(question);
questionDao.delete(question);
jpaTemplate.flush();
int rowCount = super.simpleJdbcTemplate.queryForInt(
"SELECT count(*) FROM question WHERE ques_id = 4");
assertEquals(0, rowCount);
}
@Test
public void questionWithTagsIsDeleted() {
assertEquals(3, associationTableRowCount(3));
Question question = questionDao.getById(3);
assertNotNull(question);
questionDao.delete(question);
jpaTemplate.flush();
assertEquals(0, associationTableRowCount(3));
}
private int associationTableRowCount(int questionId) {
int associationTableRows = simpleJdbcTemplate.queryForInt(
"SELECT count(*) FROM question_tag WHERE ques_id = ?", questionId);
return associationTableRows;
}
}
JpaTagDaoTest.java
package com.lishman.qa.data.jpa;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import com.lishman.qa.data.DatabaseTest;
import com.lishman.qa.data.TagDao;
import com.lishman.qa.domain.Tag;
@ContextConfiguration("JpaDaoTest-context.xml")
public class JpaTagDaoTest extends DatabaseTest {
@Autowired
private TagDao tagDao;
@Autowired
private JpaTemplate jpaTemplate;
@Test
public void correctNumberOfRowsAreReturned() {
assertEquals(super.countRowsInTable("TAG"), tagDao.getAll().size());
}
@Test
public void tagIsReturnedForSpecifiedId() {
Tag tag = tagDao.getById(1);
assertEquals(new Integer(1), tag.getId());
assertEquals("First Tag", tag.getName());
assertEquals("Description of First Tag", tag.getDescription());
assertEquals(3, tag.getQuestions().size());
}
@Test
public void tagIsReturnedForSpecifiedName() {
Tag tag = tagDao.getByName("Second Tag");
assertEquals(new Integer(2), tag.getId());
assertEquals("Second Tag", tag.getName());
assertEquals("Description of Second Tag", tag.getDescription());
assertEquals(2, tag.getQuestions().size());
}
@Test
public void newValidTagIsCreated() {
Tag tag = new Tag("New Tag Name", "New Tag description");
Errors errors = new BeanPropertyBindingResult(tag, "tag");
Tag savedTag = tagDao.save(tag, errors);
jpaTemplate.flush();
assertEquals(0, errors.getAllErrors().size());
Integer currId = super.getCurrentSequenceValue("TAG_SEQ1");
currId *= 50; assertEquals(currId, savedTag.getId());
assertEquals(currId, savedTag.getId());
assertEquals("New Tag Name", savedTag.getName());
assertEquals("New Tag description" , savedTag.getDescription());
Tag newTag = super.getTagById(currId);
assertEquals(currId, newTag.getId());
assertEquals("New Tag Name", newTag.getName());
assertEquals("New Tag description" , newTag.getDescription());
}
@Test
public void newTagWithNoNameIsRejected() {
Tag tag = new Tag(null, "New Tag description");
Errors errors = new BeanPropertyBindingResult(tag, "tag");
Tag savedTag = tagDao.save(tag, errors);
jpaTemplate.flush();
assertNull(savedTag);
assertEquals(1, errors.getAllErrors().size());
assertEquals("NotBlank", errors.getFieldError("name").getCode());
}
@Test
public void newTagWithDuplicateNameIsRejected() {
Tag tag = new Tag("First Tag", "Description");
BindingResult result = new BeanPropertyBindingResult(tag, "tag");
Tag savedTag = tagDao.save(tag, result);
assertNull(savedTag);
assertEquals(1, result.getAllErrors().size());
assertTrue(result.hasFieldErrors("name"));
assertEquals("exists", result.getFieldError("name").getDefaultMessage());
}
@Test
public void existingValidTagIsUpdated() {
Tag tag = tagDao.getById(1);
assertEquals((Integer) 1, tag.getId());
assertEquals("First Tag", tag.getName());
assertEquals("Description of First Tag", tag.getDescription());
tag.setName("Updated Name");
tag.setDescription("Updated Description");
Errors errors = new BeanPropertyBindingResult(tag, "tag");
Tag savedTag = tagDao.save(tag, errors);
jpaTemplate.flush();
assertEquals(0, errors.getAllErrors().size());
assertEquals((Integer) 1, savedTag.getId());
assertEquals("Updated Name", savedTag.getName());
assertEquals("Updated Description", savedTag.getDescription());
Tag newTag = super.getTagById(1);
assertEquals((Integer) 1, newTag.getId());
assertEquals("Updated Name", newTag.getName());
assertEquals("Updated Description", newTag.getDescription());
}
@Test
public void existingTagWithNoNameIsRejected() {
Tag tag = tagDao.getById(1);
assertEquals((Integer) 1, tag.getId());
assertEquals("First Tag", tag.getName());
assertEquals("Description of First Tag", tag.getDescription());
tag.setName(null);
Errors errors = new BeanPropertyBindingResult(tag, "tag");
Tag savedTag = tagDao.save(tag, errors);
assertNull(savedTag);
assertEquals(1, errors.getAllErrors().size());
assertEquals("NotBlank", errors.getFieldError("name").getCode());
}
@Test
public void existingTagWithDuplicateNameIsRejected() {
Tag tag = new Tag("Second Tag", "Description");
Errors errors = new BeanPropertyBindingResult(tag, "tag");
Tag savedTag = tagDao.save(tag, errors);
assertNull(savedTag);
assertEquals(1, errors.getAllErrors().size());
assertTrue(errors.hasFieldErrors("name"));
assertEquals("exists", errors.getFieldError("name").getDefaultMessage());
}
@Test
public void tagWithNoQuestionsIsDeleted() {
Tag tag = tagDao.getById(4);
assertNotNull(tag);
Errors errors = new BeanPropertyBindingResult(tag, "tag");
tagDao.delete(tag, errors);
jpaTemplate.flush();
int rowCount = super.simpleJdbcTemplate.queryForInt(
"SELECT count(*) FROM tag WHERE tag_id = 4");
assertEquals(0, rowCount);
}
@Test
public void tagWithQuestionsIsNotDeleted() {
Tag tag = tagDao.getById(3);
assertNotNull(tag);
Errors errors = new BeanPropertyBindingResult(tag, "tag");
tagDao.delete(tag, errors);
assertEquals(1, errors.getAllErrors().size());
assertTrue(errors.hasGlobalErrors());
assertEquals("has questions", errors.getGlobalError().getDefaultMessage());
}
}
DatabaseConstraintTest.java
package com.lishman.qa.data;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.test.context.ContextConfiguration;
import com.lishman.qa.domain.Question;
import com.lishman.qa.domain.Tag;
@ContextConfiguration("DatabaseTest-context.xml")
public class DatabaseConstraintTest extends DatabaseTest {
@Test
public void tagIdIsUsedIfSpecified() {
simpleJdbcTemplate.update("INSERT INTO tag (tag_id, tag_name) " +
"VALUES (999, 'New Tag Name')");
Tag tag = super.getTagById(999);
assertEquals("New Tag Name", tag.getName());
}
@Test
public void tagIdIsGeneratedIfNotSpecified() {
simpleJdbcTemplate.update("INSERT INTO tag (tag_name) VALUES ('New Tag Name')");
Tag tag = super.getTagById(super.getCurrentSequenceValue("TAG_SEQ1"));
assertEquals("New Tag Name", tag.getName());
}
@Test(expected=DuplicateKeyException.class)
public void duplicateTagIdIsRejected() {
simpleJdbcTemplate.update("INSERT INTO tag (tag_id, tag_name) " +
"VALUES (999, 'New Tag Name')");
simpleJdbcTemplate.update("INSERT INTO tag (tag_id, tag_name) " +
"VALUES (999, 'Other Tag Name')");
}
@Test(expected=DuplicateKeyException.class)
public void duplicateTagNameIsRejected() {
simpleJdbcTemplate.update("INSERT INTO tag (tag_name) VALUES ('New Tag Name')");
simpleJdbcTemplate.update("INSERT INTO tag (tag_name) VALUES ('New Tag Name')");
}
@Test(expected=DataIntegrityViolationException.class)
public void tagWithNoNameIsRejected() {
simpleJdbcTemplate.update("INSERT INTO tag (tag_name) VALUES (null)");
}
@Test(expected=DataIntegrityViolationException.class)
public void tagWithBlankNameIsRejected() {
simpleJdbcTemplate.update("INSERT INTO tag (tag_name) VALUES (' ')");
}
@Test(expected=UncategorizedSQLException.class)
public void tagWithNameTooBigIsRejected() {
simpleJdbcTemplate.update("INSERT INTO tag (tag_name) VALUES (rpad('x', 21))");
}
@Test(expected=UncategorizedSQLException.class)
public void tagWithDescriptionTooBigIsRejected() {
simpleJdbcTemplate.update("INSERT INTO tag (tag_name, tag_desc) " +
"VALUES ('x', rpad('x', 1001))");
}
@Test
public void questionIdIsUsedIfSpecified() {
simpleJdbcTemplate.update("INSERT INTO question (ques_id, question, answer) " +
"VALUES (999, 'New Question', 'New Answer')");
Question question = super.getQuestionById(999);
assertEquals("New Question", question.getQuestion());
}
@Test
public void questionIdIsGeneratedIfNotSpecified() {
simpleJdbcTemplate.update("INSERT INTO question (question, answer) " +
"VALUES ('New Question', 'New Answer')");
Question question = super.getQuestionById(super.getCurrentSequenceValue("QUESTION_SEQ1"));
assertEquals("New Question", question.getQuestion());
}
@Test(expected=DuplicateKeyException.class)
public void duplicateQuestionIdIsRejected() {
simpleJdbcTemplate.update("INSERT INTO question (ques_id, question, answer) " +
"VALUES (999, 'New Question', 'New Answer')");
simpleJdbcTemplate.update("INSERT INTO question (ques_id, question, answer) " +
"VALUES (999, 'New Question', 'New Answer')");
}
@Test(expected=DataIntegrityViolationException.class)
public void questionWithNoQuestionTextIsRejected() {
simpleJdbcTemplate.update("INSERT INTO question (answer) " +
"VALUES ('New Answer')");
}
@Test(expected=DataIntegrityViolationException.class)
public void questionWithBlankQuestionTextIsRejected() {
simpleJdbcTemplate.update("INSERT INTO question (question, answer) " +
"VALUES (' ', 'New Answer')");
}
@Test(expected=DataIntegrityViolationException.class)
public void questionWithNoAnswerTextIsRejected() {
simpleJdbcTemplate.update("INSERT INTO question (question) " +
"VALUES ('New Question')");
}
@Test(expected=DataIntegrityViolationException.class)
public void questionWithBlankAnswerTextIsRejected() {
simpleJdbcTemplate.update("INSERT INTO question (question, answer) " +
"VALUES ('New Question', ' ')");
}
@Test(expected=UncategorizedSQLException.class)
public void questionWithQuestionTextTooBigIsRejected() {
simpleJdbcTemplate.update("INSERT INTO question (question, answer) " +
"VALUES (rpad('x', 4000) || 'x', 'New Answer')");
}
@Test(expected=UncategorizedSQLException.class)
public void questionWithAnswerTextTooBigIsRejected() {
simpleJdbcTemplate.update("INSERT INTO question (question, answer) " +
"VALUES ('New Question', rpad('x', 4000) || 'x')");
}
@Test(expected=DataIntegrityViolationException.class)
public void questionTagIsRejectedIfTagIdIsNull() {
simpleJdbcTemplate.update("INSERT INTO question_tag (ques_id, tag_id) " +
"VALUES (1, null)");
}
@Test(expected=DataIntegrityViolationException.class)
public void questionTagIsRejectedIfQuestionIdIsNull() {
simpleJdbcTemplate.update("INSERT INTO question_tag (ques_id, tag_id) " +
"VALUES (null, 1)");
}
@Test(expected=DataIntegrityViolationException.class)
public void questionTagIsRejectedIfTagDoesNotExist() {
simpleJdbcTemplate.update("INSERT INTO question_tag (ques_id, tag_id) " +
"VALUES (1, 999)");
}
@Test(expected=DataIntegrityViolationException.class)
public void questionTagIsRejectedIfQuestionDoesNotExist() {
simpleJdbcTemplate.update("INSERT INTO question_tag (ques_id, tag_id) " +
"VALUES (999, 1)");
}
@Test(expected=DuplicateKeyException.class)
public void duplicateQuestionTagCombinationIsRejected() {
simpleJdbcTemplate.update("INSERT INTO question_tag (ques_id, tag_id) " +
"VALUES (3, 3)");
simpleJdbcTemplate.update("INSERT INTO question_tag (ques_id, tag_id) " +
"VALUES (3, 3)");
}
@Test(expected=DataIntegrityViolationException.class)
public void tagWithAssociatedQuestionsCannotBeDeleted() {
simpleJdbcTemplate.update("DELETE FROM tag WHERE tag_id = 1");
}
@Test(expected=DataIntegrityViolationException.class)
public void questionWithAssociatedTagsCannotBeDeleted() {
simpleJdbcTemplate.update("DELETE FROM question WHERE ques_id = 1");
}
}
DatabaseTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="databaseUtils"
class="com.lishman.qa.data.DatabaseUtils"
p:dataSource-ref="dataSource"/>
<context:property-placeholder location="classpath:data/jdbc-test.properties"/>
<import resource="classpath:data/datasource-config.xml"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
</beans>
DatabaseTest.java
package com.lishman.qa.data;
import java.util.List;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import com.lishman.qa.domain.Question;
import com.lishman.qa.domain.Tag;
@ContextConfiguration
public class DatabaseTest extends AbstractTransactionalJUnit4SpringContextTests {
protected int getCurrentSequenceValue(String sequenceName) {
return simpleJdbcTemplate.queryForInt("SELECT " + sequenceName + ".currval FROM dual");
}
protected Tag getTagById(int tagId) {
return super.simpleJdbcTemplate.queryForObject(
"SELECT tag_id AS id, tag_name AS name, tag_desc AS description " +
"FROM tag " +
"WHERE tag_id = ?",
new BeanPropertyRowMapper<Tag>(Tag.class), tagId);
}
protected Question getQuestionById(int questionId) {
return super.simpleJdbcTemplate.queryForObject(
"SELECT ques_id AS id, question, answer " +
"FROM question " +
"WHERE ques_id = ?",
new BeanPropertyRowMapper<Question>(Question.class), questionId);
}
protected List<Tag> getTagsForQuestion(int questionId) {
List<Tag> tags = super.simpleJdbcTemplate.query(
"SELECT tag_id AS id, tag_name AS name, tag_desc AS desription " +
"FROM question" +
" JOIN question_tag USING (ques_id)" +
" JOIN tag USING (tag_id) " +
"WHERE ques_id = ? " +
"ORDER BY tag_name",
new BeanPropertyRowMapper<Tag>(Tag.class), questionId);
return tags;
}
}
DatabaseUtils.java
package com.lishman.qa.data;
import java.io.File;
import java.util.List;
import javax.sql.DataSource;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
public class DatabaseUtils {
private SimpleJdbcTemplate jdbc;
@Autowired
public void setDataSource(DataSource dataSource) {
jdbc = new SimpleJdbcTemplate(dataSource);
}
public static void runSqlFromFile(String fileName) {
ApplicationContext context = new FileSystemXmlApplicationContext("classpath:com/lishman/qa/data/DatabaseTest-context.xml");
DatabaseUtils dbUtil = context.getBean("databaseUtils", DatabaseUtils.class);
dbUtil.runSqlStatementsFromFile(fileName);
}
public void runSqlStatementsFromFile(String fileName) {
try {
List<String> lines = FileUtils.readLines(new File(fileName));
for (String line : lines) {
if (StringUtils.isNotBlank(line) && !line.trim().startsWith("--")) {
String sql = StringUtils.chomp(line.trim(), ";");
jdbc.update(sql);
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
DomainTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<import resource="classpath:domain-config.xml"/>
</beans>
QuestionValidatorTest.java
package com.lishman.qa.domain;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("DomainTest-context.xml")
public class QuestionValidatorTest {
@Autowired
DomainValidator validator;
@Test
public void validQuestionAndAnswerIsAccepted() {
Question question = new Question("question", "answer");
Errors errors = new BeanPropertyBindingResult(question, "question");
validator.validate(question, errors);
assertEquals(0, errors.getAllErrors().size());
}
@Test
public void questionWithNullValuesIsRejected() {
Question question = new Question(null, null);
Errors errors = new BeanPropertyBindingResult(question, "question");
validator.validate(question, errors);
assertEquals(2, errors.getAllErrors().size());
assertEquals("NotBlank", errors.getFieldError("question").getCode());
assertEquals("NotBlank", errors.getFieldError("answer").getCode());
}
@Test
public void questionWithEmptyValuesIsRejected() {
Question question = new Question("", "");
Errors errors = new BeanPropertyBindingResult(question, "question");
validator.validate(question, errors);
assertEquals(2, errors.getAllErrors().size());
assertEquals("NotBlank", errors.getFieldError("question").getCode());
assertEquals("NotBlank", errors.getFieldError("answer").getCode());
}
@Test
public void questionWithBlankValuesIsRejected() {
Question question = new Question("", "");
Errors errors = new BeanPropertyBindingResult(question, "question");
validator.validate(question, errors);
assertEquals(2, errors.getAllErrors().size());
assertEquals("NotBlank", errors.getFieldError("question").getCode());
assertEquals("NotBlank", errors.getFieldError("answer").getCode());
}
}
TagValidatorTest.java
package com.lishman.qa.domain;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("DomainTest-context.xml")
public class TagValidatorTest {
@Autowired
DomainValidator validator;
@Test
public void tagWithNameIsAccepted() {
Tag tag = new Tag("Name", "Description");
Errors errors = new BeanPropertyBindingResult(tag, "tag");
validator.validate(tag, errors);
assertEquals(0, errors.getAllErrors().size());
}
@Test
public void tagWithNullNameIsRejected() {
Tag tag = new Tag(null, "Description");
Errors errors = new BeanPropertyBindingResult(tag, "tag");
validator.validate(tag, errors);
assertEquals(1, errors.getAllErrors().size());
assertEquals("NotBlank", errors.getFieldError("name").getCode());
}
@Test
public void tagWithEmptyNameIsRejected() {
Tag tag = new Tag("", "Description");
Errors errors = new BeanPropertyBindingResult(tag, "tag");
validator.validate(tag, errors);
assertEquals(1, errors.getAllErrors().size());
assertEquals("NotBlank", errors.getFieldError("name").getCode());
}
@Test
public void tagWithBlankNameIsRejected() {
Tag tag = new Tag(" ", "Description");
Errors errors = new BeanPropertyBindingResult(tag, "tag");
validator.validate(tag, errors);
assertEquals(1, errors.getAllErrors().size());
assertEquals("NotBlank", errors.getFieldError("name").getCode());
}
@Test
public void tagWithDescriptionIsAccepted() {
Tag tag = new Tag("Name", "Description");
Errors errors = new BeanPropertyBindingResult(tag, "tag");
validator.validate(tag, errors);
assertEquals(0, errors.getAllErrors().size());
}
@Test
public void tagWithNullDescriptionIsAccepted() {
Tag tag = new Tag("Name", null);
Errors errors = new BeanPropertyBindingResult(tag, "tag");
validator.validate(tag, errors);
assertEquals(0, errors.getAllErrors().size());
}
@Test
public void tagWithEmptyDescriptionIsAccepted() {
Tag tag = new Tag("Name", "");
Errors errors = new BeanPropertyBindingResult(tag, "tag");
validator.validate(tag, errors);
assertEquals(0, errors.getAllErrors().size());
}
@Test
public void tagWithBlankDescriptionIsAccepted() {
Tag tag = new Tag("Name", " ");
Errors errors = new BeanPropertyBindingResult(tag, "tag");
validator.validate(tag, errors);
assertEquals(0, errors.getAllErrors().size());
}
}
QaServiceStub.java
package com.lishman.qa.service;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.validation.Errors;
import com.lishman.qa.domain.DomainValidator;
import com.lishman.qa.domain.Question;
import com.lishman.qa.domain.Tag;
@Service
@Primary
public class QaServiceStub implements QaService {
@Autowired
private DomainValidator validator;
private static List<Tag> tags = new ArrayList<Tag>();
private static int tagId = 0;
private static List<Question> questions = new ArrayList<Question>();
private static int questionId = 0;
static {
reset();
}
@Override
public List<Question> getQuestions() {
List<Question> questionsCopy = new ArrayList<Question>();
for (Question question : questions) {
questionsCopy.add(copyOf(question));
}
return questionsCopy;
}
@Override
public List<Question> getShuffledQuestions() {
return getQuestions();
}
@Override
public Question getQuestionById(int questionId) {
for (Question question : questions) {
if (question.getId() == questionId) {
return copyOf(question);
}
}
return null;
}
@Override
public Question saveQuestion(Question question, Errors errors) {
validator.validate(question, errors);
if (errors.hasErrors()) {
return null;
}
Tag existingQuestion = this.getTagByName(question.getQuestion());
if (existingQuestion != null
&& (question.isNew() ||
!question.getId().equals(existingQuestion.getId()))) {
errors.rejectValue("name", "validation.exists", "exists");
return null;
}
if (question.isNew()) {
question.setId(++questionId);
} else {
deleteQuestion(question);
}
questions.add(question);
return question;
}
@Override
public void deleteQuestion(Question question) {
Iterator<Question> iter = questions.iterator();
while (iter.hasNext()) {
if (iter.next().getId() == question.getId()) {
iter.remove();
}
}
}
@Override
public List<Tag> getTags() {
return tags;
}
@Override
public Tag getTagById(int tagId) {
for (Tag tag : tags) {
if (tag.getId() == tagId) {
return copyOf(tag);
}
}
return null;
}
@Override
public Tag getTagByName(String tagName) {
for (Tag tag : tags) {
if (StringUtils.equals(tag.getName(), tagName)) {
return copyOf(tag);
}
}
return null;
}
@Override
public Tag saveTag(Tag tag, Errors errors) {
validator.validate(tag, errors);
if (errors.hasErrors()) {
return null;
}
Tag existingTag = this.getTagByName(tag.getName());
if (existingTag != null
&& (tag.isNew() ||
!tag.getId().equals(existingTag.getId()))) {
errors.rejectValue("name", "validation.exists", "exists");
return null;
}
if (tag.isNew()) {
tag.setId(++tagId);
} else {
deleteTag(tag, errors);
}
tags.add(tag);
return tag;
}
@Override
public void deleteTag(Tag tag, Errors errors) {
Iterator<Tag> iter = tags.iterator();
while (iter.hasNext()) {
if (iter.next().getId() == tag.getId()) {
iter.remove();
}
}
}
public static void reset() {
tagId = 0;
tags.clear();
Tag tagOne = createTag("one");
tags.add(tagOne);
Tag tagTwo = createTag("two");
tags.add(tagTwo);
questionId = 0;
questions.clear();
Question questionOne = createQuestion("one");
SortedSet<Tag> tagsForQuestionOne = new TreeSet<Tag>();
tagsForQuestionOne.add(tagOne);
questionOne.setTags(tagsForQuestionOne);
questions.add(questionOne);
Question questionTwo = createQuestion("two");
SortedSet<Tag> tagsForQuestionTwo = new TreeSet<Tag>();
tagsForQuestionTwo.add(tagOne);
tagsForQuestionTwo.add(tagTwo);
questionTwo.setTags(tagsForQuestionTwo);
questions.add(questionTwo);
}
public static Question createQuestion (String number) {
Question question = new Question("question " + number, "answer " + number);
question.setId(++questionId);
return question;
}
public static Tag createTag (String name) {
Tag tag = new Tag(name, "description " + name);
tag.setId(++tagId);
return tag;
}
public Question getQuestionByText(String questionText) {
for (Question question : questions) {
if (StringUtils.equals(question.getQuestion(), questionText)) {
return question;
}
}
return null;
}
private Tag copyOf (Tag source) {
Tag target = new Tag(source.getName(), source.getDescription());
target.setId(source.getId());
return target;
}
private Question copyOf (Question source) {
Question target = new Question(source.getQuestion(), source.getAnswer());
target.setId(source.getId());
if (source.getTags() != null) {
SortedSet<Tag> targetTags = new TreeSet<Tag>();
for (Tag tag : source.getTags()) {
targetTags.add(copyOf(tag));
}
target.setTags(targetTags);
}
return target;
}
}
QaServiceTest.java
package com.lishman.qa.service;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.validation.BindingResult;
import com.lishman.qa.data.QuestionDao;
import com.lishman.qa.data.TagDao;
import com.lishman.qa.domain.Question;
import com.lishman.qa.domain.Tag;
@RunWith(MockitoJUnitRunner.class)
public class QaServiceTest {
@Mock private QuestionDao questionDao;
@Mock private TagDao tagDao;
@Mock private BindingResult bindingResult;
@InjectMocks private QaServiceImpl qaService = new QaServiceImpl();
@Test
public void allQuestionsAreReturned() {
qaService.getQuestions();
verify(questionDao).getAll();
}
@Test(timeout=5000)
public void questionsAreReturnedInRandomOrder() {
List<Question> mockQuestions = new ArrayList<Question>();
mockQuestions.add(new Question("one", "answer one"));
mockQuestions.add(new Question("two", "answer two"));
when(qaService.getQuestions()).thenReturn(mockQuestions);
String firstRun = qaService.getShuffledQuestions().get(0).getQuestion();
while (true) {
String secondRun = qaService.getShuffledQuestions().get(0).getQuestion();
if (!firstRun.equals(secondRun)) {
break;
}
};
}
@Test
public void questionIsReturnedForSpecifiedId() {
qaService.getQuestionById(123);
verify(questionDao).getById(123);
}
@Test
public void questionDaoIsCalledWhenSaving() {
Question question = new Question();
qaService.saveQuestion(question, bindingResult);
verify(questionDao).save(question, bindingResult);
}
@Test
public void questionDaoIsCalledWhenDeleting() {
Question question = new Question("question", "description");
qaService.deleteQuestion(question);
verify(questionDao).delete(question);
}
@Test
public void allTagsAreReturned() {
qaService.getTags();
verify(tagDao).getAll();
}
@Test
public void tagIsReturnedForSpecifiedId() {
qaService.getTagById(123);
verify(tagDao).getById(123);
}
@Test
public void tagDaoIsCalledWhenSaving() {
Tag tag = new Tag();
qaService.saveTag(tag, bindingResult);
verify(tagDao).save(tag, bindingResult);
}
@Test
public void tagDaoIsCalledWhenDeleting() {
Tag tag = new Tag("tag", "description");
qaService.deleteTag(tag, bindingResult);
verify(tagDao).delete(tag, bindingResult);
}
}
BrowserTestSuite.java
package com.lishman.qa.testsuites;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import com.lishman.qa.browser.BrowserTest;
import com.lishman.qa.browser.MainMenuTest;
import com.lishman.qa.browser.QuestionDetailsTest;
import com.lishman.qa.browser.QuestionListTest;
import com.lishman.qa.browser.QuestionMaintenanceTest;
import com.lishman.qa.browser.TagDetailsTest;
import com.lishman.qa.browser.TagListTest;
import com.lishman.qa.browser.TagMaintenanceTest;
import com.lishman.qa.browser.TakeATestTest;
@RunWith(Suite.class)
@SuiteClasses({
MainMenuTest.class,
QuestionListTest.class,
QuestionDetailsTest.class,
QuestionMaintenanceTest.class,
TagListTest.class,
TagDetailsTest.class,
TagMaintenanceTest.class,
TakeATestTest.class,
})
public class BrowserTestSuite {
private static WebDriver driver = new FirefoxDriver();
@BeforeClass
public static void databaseSetUp() {
BrowserTest.setDriver(driver);
}
@AfterClass
public static void tearDown() {
driver.quit();
}
}
DataTestSuite.java
package com.lishman.qa.testsuites;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import com.lishman.qa.data.DatabaseConstraintTest;
import com.lishman.qa.data.DatabaseUtils;
import com.lishman.qa.data.jpa.JpaQuestionDaoTest;
import com.lishman.qa.data.jpa.JpaTagDaoTest;
@RunWith(Suite.class)
@SuiteClasses({
DatabaseConstraintTest.class,
JpaQuestionDaoTest.class,
JpaTagDaoTest.class,
})
public class DataTestSuite {
@BeforeClass
public static void databaseSetUp() {
DatabaseUtils.runSqlFromFile("sql/unit-test-data.sql");
}
}
FullTestSuite.java
package com.lishman.qa.testsuites;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
UnitTestSuite.class,
IntegrationTestSuite.class,
})
public class FullTestSuite {
}
IntegrationTestSuite.java
package com.lishman.qa.testsuites;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
BrowserTestSuite.class,
})
public class IntegrationTestSuite {
}
UnitTestSuite.java
package com.lishman.qa.testsuites;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import com.lishman.qa.domain.QuestionValidatorTest;
import com.lishman.qa.domain.TagValidatorTest;
import com.lishman.qa.service.QaServiceTest;
import com.lishman.qa.web.QuestionControllerTest;
import com.lishman.qa.web.QuestionFormTest;
import com.lishman.qa.web.TagControllerTest;
import com.lishman.qa.web.TagFormTest;
import com.lishman.qa.web.TagPropertyEditorTest;
@RunWith(Suite.class)
@SuiteClasses({
QaServiceTest.class,
QuestionControllerTest.class,
QuestionFormTest.class,
TagControllerTest.class,
TagFormTest.class,
TagPropertyEditorTest.class,
QuestionValidatorTest.class,
TagValidatorTest.class,
})
public class UnitTestSuite {
}
QuestionControllerTest.java
package com.lishman.qa.web;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.SimpleSessionStatus;
import com.lishman.qa.domain.Question;
import com.lishman.qa.service.QaService;
@RunWith(MockitoJUnitRunner.class)
public class QuestionControllerTest {
@Mock private QaService qaService;
@InjectMocks private QuestionController questionController = new QuestionController();
@Test
public void allQuestionsAreReturned() {
List<Question> questions = Collections.singletonList(new Question());
when(qaService.getQuestions()).thenReturn(questions);
List<Question> returnedQuetions = questionController.getQuestions();
assertThat(returnedQuetions, sameInstance(questions));
}
@Test
public void askQuestionForSpecifiedId() {
Question question = new Question();
when(qaService.getQuestionById(123)).thenReturn(question);
Model model = new ExtendedModelMap();
String viewName = questionController.askQuestion(123, model);
assertThat(viewName, equalTo("question-details"));
assertThat((Question) model.asMap().get("question"), sameInstance(question));
}
@Test
public void questionsAreAsked() {
final Question q1 = new Question("question1", "answer1");
final Question q2 = new Question("question2", "answer2");
List<Question> mockQuestions = new ArrayList<Question>();
mockQuestions.add(q1);
mockQuestions.add(q2);
when(qaService.getShuffledQuestions()).thenReturn(mockQuestions);
Model model = new ExtendedModelMap();
SessionStatus status = new SimpleSessionStatus();
assertThat(questionController.askQuestion(model, status), equalTo("ask-question"));
Question returnedQ1 = (Question) model.asMap().get("question");
assertThat(status.isComplete(), equalTo(false));
assertThat(questionController.askQuestion(model, status), equalTo("ask-question"));
Question returnedQ2 = (Question) model.asMap().get("question");
assertThat(status.isComplete(), equalTo(false));
assertThat(questionController.askQuestion(model, status), equalTo("main-menu"));
assertThat(status.isComplete(), equalTo(true));
assertThat(returnedQ1, not(sameInstance(returnedQ2)));
List<Question> questions = new ArrayList<Question>();
questions.add(q1);
questions.add(q2);
assertThat(questions, hasItem(returnedQ1));
assertThat(questions, hasItem(returnedQ2));
}
}
QuestionFormTest.java
package com.lishman.qa.web;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.SimpleSessionStatus;
import com.lishman.qa.domain.Question;
import com.lishman.qa.service.QaService;
@RunWith(MockitoJUnitRunner.class)
public class QuestionFormTest {
@Mock private QaService qaService;
@Mock private BindingResult bindingResult;
@InjectMocks private QuestionForm questionForm = new QuestionForm();
@Test
public void emptyQuestionObjectIsReturnedWhenNoIdIsSupplied() {
Question question = questionForm.setUpForm(null);
assertNull(question.getId());
assertNull(question.getQuestion());
assertNull(question.getAnswer());
verify(qaService, never()).getQuestionById(anyInt());
}
@Test
public void populatedQuestionObjectIsReturnedWhenIdIsSupplied() {
Question question = new Question();
when(qaService.getQuestionById(123)).thenReturn(question);
Question returnedQuestion = questionForm.setUpForm(123);
assertThat(returnedQuestion, sameInstance(question));
}
@Test
public void questionListIsDisplayedAfterSuccessfulSave() {
Question question = new Question();
SessionStatus sessionStatus = new SimpleSessionStatus();
when(bindingResult.hasErrors()).thenReturn(false);
String viewName = questionForm.save(question, bindingResult, sessionStatus);
assertThat(viewName, equalTo("redirect:question-list"));
verify(qaService).saveQuestion(question, bindingResult);
assertThat(sessionStatus.isComplete(), equalTo(true));
}
@Test
public void questionFormIsDisplayedAfterFailedSave() {
Question question = new Question();
SessionStatus sessionStatus = new SimpleSessionStatus();
when(bindingResult.hasErrors()).thenReturn(true);
String viewName = questionForm.save(question, bindingResult, sessionStatus);
assertThat(viewName, equalTo("question-form"));
verify(qaService).saveQuestion(question, bindingResult);
assertThat(sessionStatus.isComplete(), equalTo(false));
}
@Test
public void questionListIsDisplayedAfterSuccessfulDelete() {
Question question = new Question();
Model model = new ExtendedModelMap();
model.addAttribute(question);
SessionStatus sessionStatus = new SimpleSessionStatus();
when(bindingResult.hasErrors()).thenReturn(false);
String viewName = questionForm.delete(model, sessionStatus);
assertThat(viewName, equalTo("redirect:question-list"));
verify(qaService).deleteQuestion(question);
assertThat(sessionStatus.isComplete(), equalTo(true));
}
}
StubController.java
package com.lishman.qa.web;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.lishman.qa.domain.Question;
import com.lishman.qa.domain.Tag;
import com.lishman.qa.service.QaServiceStub;
@Controller
public class StubController {
@Autowired(required=false)
private QaServiceStub qaService;
@RequestMapping(value="/reset", method=RequestMethod.POST)
@ResponseBody public String reset() {
QaServiceStub.reset();
return "ok";
}
@RequestMapping(value="/get-tag/{name}", method=RequestMethod.GET)
@ResponseBody public Tag getTag(@PathVariable String name) {
return qaService.getTagByName(name);
}
@RequestMapping(value="/get-tags", method=RequestMethod.GET)
@ResponseBody public List<Tag> getTags() {
return qaService.getTags();
}
@RequestMapping(value="/get-question/{question}", method=RequestMethod.GET)
@ResponseBody public Question getQuestionByText(@PathVariable String question) {
return qaService.getQuestionByText(question);
}
@RequestMapping(value="/get-questions", method=RequestMethod.GET)
@ResponseBody public List<Question> getQuestions() {
return qaService.getQuestions();
}
}
TagControllerTest.java
package com.lishman.qa.web;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import com.lishman.qa.domain.Tag;
import com.lishman.qa.service.QaService;
@RunWith(MockitoJUnitRunner.class)
public class TagControllerTest {
@Mock private QaService qaService;
@InjectMocks private TagController tagController = new TagController();
@Test
public void allTagsAreReturned() {
List<Tag> tags = Collections.singletonList(new Tag());
when(qaService.getTags()).thenReturn(tags);
List<Tag> returnedTags = tagController.getTags();
assertThat(returnedTags, sameInstance(tags));
}
@Test
public void tagIsReturnedForSpecifiedId() {
Tag tag = new Tag();
when(qaService.getTagById(123)).thenReturn(tag);
Model model = new ExtendedModelMap();
String viewName = tagController.getTag(123, model);
assertThat(viewName, equalTo("tag-details"));
assertThat((Tag) model.asMap().get("tag"), sameInstance(tag));
}
}
TagFormTest.java
package com.lishman.qa.web;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.SimpleSessionStatus;
import com.lishman.qa.domain.Tag;
import com.lishman.qa.service.QaService;
@RunWith(MockitoJUnitRunner.class)
public class TagFormTest {
@Mock private QaService qaService;
@Mock private BindingResult bindingResult;
@Mock private WebDataBinder webDataBinder;
@InjectMocks private TagForm tagForm = new TagForm();
@Test
public void dataBinderIsSetupCorrectly() {
tagForm.initBinder(webDataBinder);
verify(webDataBinder).setDisallowedFields("id");
}
@Test
public void emptyTagObjectIsReturnedWhenNoIdIsSupplied() {
Tag tag = tagForm.setUpForm(null);
assertThat(tag.getId(), is(nullValue()));
assertThat(tag.getName(), is(nullValue()));
assertThat(tag.getDescription(), is(nullValue()));
verify(qaService, never()).getTagById(anyInt());
}
@Test
public void populatedTagObjectIsReturnedWhenIdIsSupplied() {
Tag tag = new Tag();
when(qaService.getTagById(123)).thenReturn(tag);
Tag returnedTag = tagForm.setUpForm(123);
assertThat(returnedTag, sameInstance(tag));
}
@Test
public void tagListViewIsDisplayedAfterSuccessfulSave() {
Tag tag = new Tag();
SessionStatus sessionStatus = new SimpleSessionStatus();
when(bindingResult.hasErrors()).thenReturn(false);
String viewName = tagForm.save(tag, bindingResult, sessionStatus);
assertThat(viewName, equalTo("redirect:tag-list"));
verify(qaService).saveTag(tag, bindingResult);
assertThat(sessionStatus.isComplete(), equalTo(true));
}
@Test
public void tagFormIsDisplayedAfterFailedSave() {
Tag tag = new Tag();
SessionStatus sessionStatus = new SimpleSessionStatus();
when(bindingResult.hasErrors()).thenReturn(true);
String viewName = tagForm.save(tag, bindingResult, sessionStatus);
assertThat(viewName, equalTo("tag-form"));
verify(qaService).saveTag(tag, bindingResult);
assertThat(sessionStatus.isComplete(), equalTo(false));
}
@Test
public void tagListViewIsDisplayedAfterSuccessfulDelete() {
Tag tag = new Tag();
SessionStatus sessionStatus = new SimpleSessionStatus();
when(bindingResult.hasErrors()).thenReturn(false);
String viewName = tagForm.delete(tag, bindingResult, sessionStatus);
assertThat(viewName, equalTo("redirect:tag-list"));
verify(qaService).deleteTag(tag, bindingResult);
assertThat(sessionStatus.isComplete(), equalTo(true));
}
@Test
public void tagFormIsDisplayedAfterFailedDelete() {
Tag tag = new Tag();
SessionStatus sessionStatus = new SimpleSessionStatus();
when(bindingResult.hasErrors()).thenReturn(true);
String viewName = tagForm.delete(tag, bindingResult, sessionStatus);
assertThat(viewName, equalTo("tag-form"));
verify(qaService).deleteTag(tag, bindingResult);
assertThat(sessionStatus.isComplete(), equalTo(false));
}
}
TagPropertyEditorTest.java
package com.lishman.qa.web;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import com.lishman.qa.domain.Tag;
import com.lishman.qa.service.QaService;
@RunWith(MockitoJUnitRunner.class)
public class TagPropertyEditorTest {
@Mock private QaService qaService;
@Test
public void tagNameIsConvertedToTag() {
Tag tag = new Tag("xyz", "xyz description");
when(qaService.getTagByName(anyString())).thenReturn(tag);
TagPropertyEditor propEd = new TagPropertyEditor(qaService);
propEd.setAsText("tagname");
Tag returnedTag = (Tag) propEd.getValue();
assertThat(returnedTag, equalTo(tag));
}
}
TestExt.java
package com.lishman.qa;
public class TestExt {
public <T> T getPrivateFieldValue(Object obj, String fieldName) {
return TestUtils.getPrivateFieldValue(obj, fieldName);
}
public void setPrivateFieldValue(Object obj, String fieldName, Object val) {
TestUtils.setPrivateFieldValue(obj, fieldName, val);
}
}
TestUtils.java
package com.lishman.qa;
import java.lang.reflect.Field;
public class TestUtils {
@SuppressWarnings("unchecked")
public static <T> T getPrivateFieldValue(Object obj, String fieldName) {
try {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void setPrivateFieldValue(Object obj, String fieldName, Object val) {
try {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, val);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
base.css
/* General */
body {
font-family: Geneva, Arial, Helvetica, sans-serif;
}
img {
border-style: none;
}
/* Links */
a {
font-weight: normal;
text-decoration: none;
}
a:hover {
font-weight: normal;
text-decoration: none;
}
/* Button */
.button {
margin-left: 235px;
padding: 10px;
border: 1px solid DarkGray;
text-decoration: none;
border-radius: 5px 5px 5px 5px;
cursor: pointer;
}
/* Headers */
h1, h2 {
font-weight: normal;
margin: 0;
padding: 0;
}
h1 {
font-size: 20px;
padding-top: 10px;
padding-bottom: 10px;
}
h2 {
font-size: 18px;
padding-top: 10px;
padding-bottom: 10px;
}
/* Contents */
#contents {
margin-left: 20;
}
/* Table */
table {
width: 100%;
}
table {
border-collapse: collapse;
margin: 2px 0 10px 0;
}
table th {
text-align: left;
}
table th, table td {
border: 1px Silver solid;
padding: 5px;
}
table#tag {
width: 400px;
}
table#question {
width: 800px;
}
/* Horizontal rule */
hr {
height: 1px;
border-style: none;
background-color: lightgray;
}
/* Questions & Answers */
.question-box, .answer-box {
height: 150px;
width: 600px;
margin: 0px;
padding: 5px;
border: 1px LightGray solid;
}
.reveal-area {
margin: 10px 0 10px 0;
padding: 10px;
}
.footer {
margin-top: 20px;
}
/* Error */
.error {
color: Red;
}
plain.css
@import url("base.css");
/* General */
body {
color: Black;
}
/* Links */
a {
color: Blue;
text-decoration: underline;
}
a > button {
text-decoration: none;
}
a:hover {
color: Blue;
text-decoration: underline;
}
/* Headers */
h1, h2 {
color: Black;
}
/* Table */
table {
background: White;
}
table th {
color: Black;
background: White;
}
silver.css
@import url("base.css");
/* General */
body {
color: DimGray;
}
/* Links */
a {
color: DarkSlateGray;
}
a:hover {
color: OrangeRed;
}
/* Headers */
h1, h2 {
color: SeaGreen;
}
/* Table */
table {
background: WhiteSmoke;
}
table th {
color: DarkSlateGray;
background: Gainsboro;
}
edit.gif
quest.js
$(document).ready(function(){
$(".answer").hide();
$('#reveal').click(function(){
$(".answer").show(500);
});
$("#reveal").hover(
function () {
$(this).css("background", "#F0F0F0");
},
function () {
$(this).css("background", "White");
}
);
});
ask-question.jsp
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ pageContext.setAttribute("linefeed", "\n"); %>
<html>
<head>
<link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script type="text/javascript" src="<c:url value='/js/quest.js' />"></script>
<title><spring:message code="test.take"></title>
</head>
<body>
<div id="contents">
<h2><spring:message code="tag.plural">:
<c:forEach items="${question.tags}" var="tag" varStatus="status">
${tag.name}${status.last ? ' ' : ','}
</c:forEach>
</h2>
<div id="question" class="question-box">
${fn:replace(question.question, linefeed, "<br/>")}
</div>
<div class="reveal-area">
<span id="reveal" class="button"><spring:message code="question.revealAnswer"></span>
</div>
<div class="answer-box">
<div id="answer" class="answer">
${fn:replace(question.answer, linefeed, "<br/>")}
</div>
</div>
<div class="footer">
<a id="menu" style="padding-right:10px" href="<c:url value='/app/main-menu.html'/>">
« <spring:message code="navigation.menu.main">
</a>|
<a id="next-quest" style="padding-left:10px" href="<c:url value='/app/ask-question.html'/>">
<spring:message code="test.nextQuestion"> »
</a>
</div>
</div>
</body>
</html>
main-menu.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css">
<title><spring:message code="application.name"></title>
</head>
<body>
<h1><spring:message code="application.name"></h1>
<div id="contents" class="menu">
<p>
<a id="take-a-test" href="<c:url value='/app/ask-question.html' />"><spring:message code="test.take"></a>
</p>
<p>
<a id="quest-maint" href="<c:url value='/app/question-list.html' />"><spring:message code="question.maintain"></a>
</p>
<p>
<a id="tag-maint" href="<c:url value='/app/tag-list.html' />"><spring:message code="tag.maintain"></a>
</p>
</div>
<div style="font-size: 80%; margin-top: 40px" class="footer">
<hr/>
<p>
<a id="theme-plain" href="?theme-name=plain"><spring:message code="theme.plain"></a> |
<a id="theme-silver" href="?theme-name=silver"><spring:message code="theme.silver"></a>
</p>
<p>
<a id="locale-en" href="?locale=en">en</a> |
<a id="locale-fr" href="?locale=fr">fr</a> |
<a id="locale-de" href="?locale=de">de</a>
</p>
</div>
</body>
</html>
question-details.jsp
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ pageContext.setAttribute("linefeed", "\n"); %>
<html>
<head>
<link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css">
<title><spring:message code="question.details"></title>
</head>
<body>
<div id="contents">
<h1><spring:message code="question.details"></h1>
<div id="question" class="question-box">${fn:replace(question.question, linefeed, "<br/>")}</div>
<br/>
<h1><spring:message code="answer.label"></h1>
<div class="answer-box">
<div id="answer" class="answer">${fn:replace(question.answer, linefeed, "<br/>")}</div>
</div>
<br/>
<h2 id="tags"><spring:message code="tag.plural">:
<c:forEach items="${question.tags}" var="tag" varStatus="status">
${tag.name}${status.last ? ' ' : ','}
</c:forEach>
</h2>
</div>
<div class="footer">
<a id="go-back" href="<c:url value='/app/question-list.html' />">
« <spring:message code="navigation.back">
</a>
</div>
</body>
</html>
question-form.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$('#delete').click(function () {
return confirm("<spring:message code='button.delete'/> <spring:message code='question.label'/>?");
});
});
</script>
<title><spring:message code="question.maintain"></title>
</head>
<body>
<h1><spring:message code="question.maintain"></h1>
<div id="contents">
<form:form id="question-form" modelAttribute="question" action="question-form" method="post">
<c:choose>
<c:when test="${question.new}">
<button id="create" type="submit" name="save">
<spring:message code="button.create">
</button>
</c:when>
<c:otherwise>
<button id="save" type="submit" name="save">
<spring:message code="button.update">
</button>
<button id="delete" type="submit" name="delete" onclick="return confirm('<spring:message code='button.delete'/>
<spring:message code='question.label'/>?')">
<spring:message code="button.delete">
</button>
</c:otherwise>
</c:choose>
<p>
<spring:message code="question.label"><br>
<form:textarea path="question" rows="5" cols="50"><br/>
<form:errors path="question" cssClass="error">
</p>
<p>
<spring:message code="answer.label"><br>
<form:textarea path="answer" rows="5" cols="50"><br/>
<form:errors path="answer" cssClass="error">
</p>
<p>
<spring:message code="tag.plural"><br>
<form:checkboxes path="tags" items="${tagList}" delimiter="<br/>">
</p>
</form:form>
</div>
<div class="footer">
<a id="cancel" href="<c:url value='/app/question-list.html' />">
« <spring:message code="navigation.cancel">
</a>
</div>
</body>
</html>
question-list.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css">
<title><spring:message code="question.plural"></title>
</head>
<body>
<h1><spring:message code="question.plural"></h1>
<div id="contents">
<a href="<c:url value='/app/question-form.html' />">
<button id="create"><spring:message code="button.create"></button>
</a>
<table id="question">
<tr>
<th style="width: 20px"> </th>
<th><spring:message code="question.label"></th>
<th><spring:message code="answer.label"></th>
</tr>
<c:forEach items="${questions}" var="question">
<tr>
<td>
<a href="<c:url value='/app/question-form.html?id=${question.id}'/>">
<img src="<c:url value='/images/edit.gif' />">
</a>
</td>
<td>
<a href="<c:url value='/app/question-details/${question.id}'/>">
${question.question}
</a>
</td>
<td>
${question.answer}
</td>
</tr>
</c:forEach>
</table>
</div>
<div class="footer">
<a id="go-back" href="<c:url value='/app/main-menu.html' />">
« <spring:message code="navigation.back">
</a>
</div>
</body>
</html>
tag-details.jsp
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css">
<title><spring:message code="tag.details"></title>
</head>
<body>
<h1><spring:message code="tag.details"></h1>
<div id="contents">
<table id="tag">
<tr>
<td><spring:message code="tag.field.name"></td>
<td><strong>${tag.name}</strong></td>
</tr>
<tr>
<td><spring:message code="tag.field.description"></td>
<td><strong>${tag.description}</strong></td>
</tr>
</table>
</div>
<div class="footer">
<a id="go-back" href="<c:url value='/app/tag-list.html' />">
« <spring:message code="navigation.back">
</a>
</div>
</body>
</html>
tag-form.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$('#delete').click(function () {
return confirm("<spring:message code='button.delete'/> <spring:message code='tag.label'/>?");
});
});
</script>
<title><spring:message code="tag.maintain"></title>
</head>
<body>
<h1><spring:message code="tag.maintain"></h1>
<div id="contents" class="detail">
<form:form modelAttribute="tag" action="tag-form" method="post">
<c:choose>
<c:when test="${tag.new}">
<button id="create" type="submit" name="save">
<spring:message code="button.create">
</button>
</c:when>
<c:otherwise>
<button id="save" type="submit" name="save">
<spring:message code="button.update">
</button>
<button id="delete" type="submit" name="delete" onclick="return confirm('<spring:message code='button.delete'/>
<spring:message code='tag.label'/>?')">
<spring:message code="button.delete">
</button>
</c:otherwise>
</c:choose>
<form:errors cssClass="error" element="p">
<p>
<spring:message code="tag.field.name"><br>
<form:input path="name"><br/>
<form:errors path="name" cssClass="error">
</p>
<p>
<spring:message code="tag.field.description"><br>
<form:textarea path="description" cols="50" rows="5"><br/>
<form:errors path="description" cssClass="error">
</p>
</form:form>
</div>
<div class="footer">
<a id="cancel" href="<c:url value='/app/tag-list.html' />">
« <spring:message code="navigation.cancel">
</a>
</div>
</body>
</html>
tag-list.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css">
<title><spring:message code="tag.plural"></title>
</head>
<body>
<h1><spring:message code="tag.plural"></h1>
<div id="contents">
<a href="<c:url value='/app/tag-form.html' />">
<button id="create"><spring:message code="button.create"></button>
</a>
<table id="tag">
<tr>
<th style="width: 20px"> </th>
<th><spring:message code="tag.field.name"></th>
<th><spring:message code="tag.field.description"></th>
</tr>
<c:forEach items="${tags}" var="tag">
<tr>
<td>
<a href="<c:url value='/app/tag-form.html?id=${tag.id}'/>">
<img src="<c:url value='/images/edit.gif' />">
</a>
</td>
<td>
<a id="back" href="<c:url value='/app/tag-details/${tag.id}'/>">
${tag.name}
</a>
</td>
<td>
${tag.description}
</td>
</tr>
</c:forEach>
</table>
</div>
<div class="footer">
<a id="go-back" href="<c:url value='/app/main-menu.html' />">
« <spring:message code="navigation.back">
</a>
</div>
</body>
</html>
app-config-with-stubs.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<import resource="classpath:mvc-config.xml"/>
<import resource="classpath:service-config-with-stubs.xml"/>
<import resource="classpath:domain-config.xml"/>
</beans>
app-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:property-placeholder location="classpath:data/jdbc.properties"/>
<import resource="classpath:mvc-config.xml"/>
<import resource="classpath:service-config.xml"/>
<import resource="classpath:data/datasource-config.xml"/>
<import resource="classpath:data/jpa-config.xml"/>
<import resource="classpath:domain-config.xml"/>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2.4"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Quest</display-name>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j.properties</param-value>
</context-param>
<context-param>
<param-name>log4jRefreshInterval</param-name>
<param-value>10000</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>qa</param-value>
</context-param>
<servlet>
<servlet-name>quest</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/app-config-with-stubs.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>quest</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>app/</welcome-file>
</welcome-file-list>
</web-app>
build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="make-war" basedir="." default="create-war">
<property name="deploy.dir" value="deploy"/>
<property name="web.dir" value="webapp"/>
<property name="web-inf.dir" value="${web.dir}/WEB-INF"/>
<property name="warfile" value="qa1"/>
<property name="lib.dir" value="${web-inf.dir}/lib"/>
<target name="clean">
<delete dir="${deploy.dir}"/>
</target>
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar"/>
<fileset dir="${jee.dir}" includes="**/*.jar"/>
</path>
<target name="compile" depends="clean">
<mkdir dir="${deploy.dir}/classes"/>
<javac srcdir="src" destdir="${deploy.dir}/classes" classpathref="classpath"/>
</target>
<target name="config">
<copy file="config/data/jdbc-live.properties"
tofile="${deploy.dir}/classes/data/jdbc.properties"/>
<copy todir="${deploy.dir}/classes">
<fileset dir="config">
<exclude name="**/jdbc*"/>
</fileset>
</copy>
<copy todir="${deploy.dir}/classes">
<fileset dir="src">
<include name="META-INF/*"/>
</fileset>
</copy>
</target>
<target name="create-war" depends="compile, config">
<war destfile="${deploy.dir}/${warfile}.war" webxml="${web-inf.dir}/web.xml">
<classes dir="${deploy.dir}/classes"/>
<fileset dir="${web.dir}">
<exclude name="WEB-INF/web.xml"/>
</fileset>
</war>
</target>
</project>