Testing with mocks only proves you know how to write mocks.
Dan Allen
Mocks are objects pre-programmed with expectations
We are not going to talk about High-Level-Tests
but about Low-Level-Tests
Avoid In
when code runs in Container
PaymentGateway.java
public class PaymentGateway {
@Inject @CreditCard PaymentProcessor paymentProcessor (1)
//...
public void setPaymentProcessor(PaymentProcessor paymentProcessor) {...}
}
1 | Qualifier Injection |
Are you sure you are setting manually the right instance? |
Next
bring tests to runtime
pom.xml
<dependencyManagement>
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>${arquillian.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.jboss.shrinkwrap</groupId>
<artifactId>shrinkwrap-bom</artifactId>
<version>${shrinkwrap.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
pom.xml
<dependencies>
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.shrinkwrap.resolver</groupId>
<artifactId>shrinkwrap-resolver-depchain</artifactId>
<type>pom</type>
<scope>test</scope>
</dependency>
InterestCalculatorTest.java
@RunWith(Arquillian.class)
@Deployment
public static JavaArchive createDeployment() { (1)
return ShrinkWrap.create(JavaArchive.class).addClass(InterestCalculator.class);
}
@EJB InterestCalculator interestCalculator; (2)
@Test
public void should_calculate_the_intereset_of_a_load() {
assertThat(interestCalculator.calculate(3600, 0.084, 24), is(12.6));
}
}
1 | Create a uDeployment Jar file in-memory |
2 | We can use EJB annotations inside test |
Avoid In
DAOs
pom.xml
<dependencies>
<dependency>
<groupId>com.lordofthejars</groupId>
<artifactId>nosqlunit-mongodb</artifactId> (1)
<version>${version.nosqlunit}</version>
</dependency>
1 | Each supported database has its own artifact |
data.json
{
"Book": [
{"title":"The Hobbit","numberOfPages":293}
]
}
tMongoDbTest.java
@Rule
public MongoDbRule remoteMongoDbRule = newMongoDbRule().defaultManagedMongoDb("db"); (1)
@Test
@UsingDataSet(locations="data.json",loadStrategy=LoadStrategyEnum.CLEAN_INSERT) (2)
@ShouldMatchDataSet(location="expectedData.json") (3)
public void book_should_be_inserted_into_repository() {
//test
}
1 | Set connection to MongoDB |
2 | Set initial dataset with clean-insert strategy |
3 | We can even assert data after test |
Avoid In
Email Services
SMTPMailService.sendMail(MailMessageBuilder$MailMessage) Transport.send(Message) Transport.send0(Message, Address[]) SMTPTransport.sendMessage(Message, Address[]) MimeMessage.writeTo(OutputStream, String[]) MimeBodyPart.writeTo(MimePart, OutputStream, String[]
pom.xml
<dependencies>
<dependency>
<groupId>org.subethamail</groupId>
<artifactId>subethasmtp</artifactId>
<version>${version.subetha}</version>
</dependency>
SmtpServiceTest.java
private static final int SMTP_PORT = 2500;
private static Wiser mailServer = new Wiser();
@BeforeClass
public static void startWiser() {
mailServer.setPort(SMTP_PORT);
mailServer.start(); (1)
}
@AfterClass
public static void stopWiser() {
mailServer.stop(); (2)
}
1 | Starts smtp service with configured parameters |
2 | Stops smtp service and sent messages are removed |
SmtpServiceTest.java
@Test
public void an_email_should_be_sent() throws MessagingException, IOException {
SMTPMailService smtpMailService = getMailService();
MailMessage message = mail().from("me@mail.com").addTo("you@mail.com").contentType("text/plain").subject("Welcome").body("Welcome to our site.").build();
smtpMailService.sendMail(message); (1)
WiserMessage sentMessage = getSentMessage(); (2)
String subject = sentMessage.getMimeMessage().getSubject();
assertThat(subject, is("Welcome"));
}
1 | Send an email using standard javax.mail API |
2 | Get the send message from service |
Avoid In
Consuming Restful Web Services
pom.xml
<dependencies>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>${version.mockserver}</version>
</dependency>
ConsumingRestServiceTest.java
private ClientAndProxy proxy = null;
@Before
public static void startMockServer() {
mockServer = startClientAndServer(8080); (1)
}
@After
public static void stopMockServer() {
mockServer.stop(); (2)
}
1 | Starts mock service with configured parameters |
2 | Stops mock service and expectations are removed |
ConsumingRestServiceTest.java
mockServer
.when(
request() (1)
.withMethod("GET")
.withPath("/login")
)
.respond( (2)
response()
.withBody("{ message: 'incorrect username and password combination' }")
);
1 | When HTTP GET is executed on localhost:8080/login |
2 | Next json message is returned |
Avoid In
UI
SeleniumTest.java
driver.get(contextPath.toString()+"login.xhtml");
WebElement username = driver.findElement(By.id("username"));
WebElement password = driver.findElement(By.id("password"));
WebElement submit = driver.findElement(By.id("submit"));
username.sendKeys("aa");
password.sendKeys("bb");
submit.click();
WebElement welcomeMessage = driver.findElement(By.id("welcomeMessage"));
assertThat(welcomeMessage.getText(), is("Welcome"));
pom.xml
<dependencyManager>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-bom</artifactId>
<version>${arquillian.drone.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.graphene</groupId>
<artifactId>graphene-webdriver</artifactId>
<version>${arquillian.graphene.version}</version>
<type>pom</type>
</dependency>
pom.xml
<dependencies>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-webdriver-depchain</artifactId>
<type>pom</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.graphene</groupId>
<artifactId>graphene-webdriver</artifactId>
<type>pom</type>
<scope>test</scope>
</dependency>
arquillian.xml
<extension qualifier="webdriver">
<property name="javascriptEnabled">true</property>
<property name="browser">${browser}</property> (1)
<property name="remoteReusable">${remoteReusable}</property>
</extension>
<extension qualifier="graphene">
<property name="waitGuiInterval">30</property>
</extension>
1 | Note that we are using system properties to set browser |
testResources should be configured with filtering in Maven. |
pom.xml
<profile>
<id>browser-firefox</id>
<properties>
<browser>firefox</browser>
</properties>
</profile>
<profile>
<id>browser-remote-reusable</id>
<properties>
<remoteReusable>true</remoteReusable>
</properties>
</profile>
also works with Google Chrome, Safari, PhantomJS, … |
TestLoginPage.java
@Deployment(testable = false) (1)
public static WebArchive createDeployment() { (2)
return Deployments.createLogin();
}
@Drone WebDriver driver; (3)
@Page TransferPage transferPage; (4)
@Test public void should_login_succesful(@InitialPage LoginPage loginPage) { (5)
loginPage.signIn("aa", "bb");
transferPage.assertOnTransferPageWithWelcomeMessage("aa");
1 | testeable=false makes test runs on client |
2 | Creating a war file in memory |
3 | Drone injects configured WebDriver |
4 | Page is a Graphene annotation that injects a page object initialized |
5 | InitialPage sets the page to be opened at the start of the test |
LoginPage.java
@Location("login.xhtml") (1)
public class LoginPage {
@FindBy private WebElement username; (2)
@FindBy private WebElement password;
@FindBy private WebElement submit;
public void signIn(String login, String password) {
this.username.sendKeys(login);
this.password.sendKeys(password);
guardHttp(this.submit).click(); (3)
}
}
1 | We set the physical page which page object maps for |
2 | FindBy maps a WebElement . If no name is provided, field name is used as id |
3 | guardHttp ensures that all communication is done |
Avoid In
Mobile
Avoid In
Race Conditions and Uncommon Exceptions
FileUtils.java
public void createFileWithContent(File filename, String content) throws IOException {
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write(content);
}
ThrowIOExceptioTest.java
Test(expected = ”IOException.class”)
@BMRule(
name="throw IOException writting content",
targetClass = "com.lordofthejars.byteman.util.FileUtils", (1)
targetMethod = "createFileWithContent", (2)
targetLocation = "CALL BufferedWriter.write(String)", (3)
action = "throw new java.io.IOException()" (4)
)
public void an_exception_should_be_thrown() throws IOException {
backupManager.backupData("Hello World");
}
1 | set the class where code will be injected |
2 | set the method where code will be injected |
3 | set in which point the code will be injected |
4 | code injected |