Resistence is Futile, Mocks Will Be Assimilated

Testing with mocks only proves you know how to write mocks.


Dan Allen

What are Mocks?

Mocks are objects pre-programmed with expectations

Some Java Mock Frameworks:

Mockito
https://code.google.com/p/mockito/
EasyMock
http://easymock.org/
jMock
http://jmock.org/

PROs

CONs

emmanuel

Is It All Lost?

We are not going to talk about High-Level-Tests

but about Low-Level-Tests

Let’s avoid mocking

scenario

Business Layer

Avoid In
when code runs in Container

Why Mocking?

Container Services

Injection

PaymentGateway.java
public class PaymentGateway {

	@Inject @CreditCard PaymentProcessor paymentProcessor (1)

	//...

	public void setPaymentProcessor(PaymentProcessor paymentProcessor) {...}
}
1Qualifier Injection
Are you sure you are setting manually the right instance?

Next
bring tests to runtime

Just real tests

Arquillian
http://arquillian.org/

Arquillian

1 select a container
2 start container
3 package test archive
4 run test in container
5 test result
6 disconnect container

Engage

Install Arquillian

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>

Install Arquillian

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>

Test

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));
    }
}
1Create a uDeployment Jar file in-memory
2We can use EJB annotations inside test

Data Layer

Avoid In
DAOs

Why Mocking?

Fast

HSQLDB
http://hsqldb.org/
MongoDB
https://github.com/fakemongo/fongo
Neo4j
http://www.neo4j.org/
Infinispan
http://infinispan.org/
Cassandra
http://cassandra.apache.org/

Isolation

DBUnit
http://dbunit.sourceforge.net/
NoSQLUnit
https://github.com/lordofthejars/nosql-unit

Engage

Install NoSQLUnit

pom.xml
<dependencies>
	<dependency>
	    <groupId>com.lordofthejars</groupId>
	    <artifactId>nosqlunit-mongodb</artifactId> (1)
	    <version>${version.nosqlunit}</version>
	</dependency>
1Each supported database has its own artifact

DataSet

data.json
{
"Book": [
			{"title":"The Hobbit","numberOfPages":293}
		]
}

Test

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
}
1Set connection to MongoDB
2Set initial dataset with clean-insert strategy
3We can even assert data after test

External Services Layer

Avoid In
Email Services

Why Mocking?

Stacktrace
SMTPMailService.sendMail(MailMessageBuilder$MailMessage)
	Transport.send(Message)
		Transport.send0(Message, Address[])
			SMTPTransport.sendMessage(Message, Address[])
				MimeMessage.writeTo(OutputStream, String[])
					MimeBodyPart.writeTo(MimePart, OutputStream, String[]

Embeddable SMTP

Subetha-Wiser
https://code.google.com/p/subethasmtp/wiki/Wiser

Engage

Install Wiser

pom.xml
<dependencies>
	<dependency>
    	<groupId>org.subethamail</groupId>
    	<artifactId>subethasmtp</artifactId>
    	<version>${version.subetha}</version>
	</dependency>

Start/Stop Wiser

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)
}
1Starts smtp service with configured parameters
2Stops smtp service and sent messages are removed

Test

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"));
}
1Send an email using standard javax.mail API
2Get the send message from service

Avoid In
Consuming Restful Web Services

Why Mocking?

Mocking Server

MockServer
http://www.mock-server.com/

MockServer

Engage

Install MockServer

pom.xml
<dependencies>
	<dependency>
     	<groupId>org.mock-server</groupId>
    	<artifactId>mockserver-netty</artifactId>
     	<version>${version.mockserver}</version>
	</dependency>

Start/Stop MockServer

ConsumingRestServiceTest.java
private ClientAndProxy proxy = null;

@Before
public static void startMockServer() {
	mockServer = startClientAndServer(8080); (1)
}

@After
public static void stopMockServer() {
	mockServer.stop(); (2)
}
1Starts mock service with configured parameters
2Stops mock service and expectations are removed

Test

ConsumingRestServiceTest.java
mockServer
        .when(
                request() (1)
                        .withMethod("GET")
                        .withPath("/login")
        )
        .respond( (2)
                response()
                        .withBody("{ message: 'incorrect username and password combination' }")
        );
1When HTTP GET is executed on localhost:8080/login
2Next json message is returned

Bonus Track

Avoid In
UI

Why Mocking?

Drive the browser and elements

Selenium 2
http://docs.seleniumhq.org/projects/webdriver/
Arquillian Drone
https://github.com/arquillian/arquillian-extension-drone
Arquillian Graphene
https://github.com/arquillian/arquillian-graphene

Arquillian Drone/Graphene

Engage

Plain Selenium

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"));

Install Arquillian Drone/Graphene

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>

Install Arquillian Drone/Graphene

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>

Configuring Arquillian Drone/Graphene

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>
1Note that we are using system properties to set browser
testResources should be configured with filtering in Maven.

Configuring Arquillian Drone/Graphene

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, …

Test

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");
1testeable=false makes test runs on client
2Creating a war file in memory
3Drone injects configured WebDriver
4Page is a Graphene annotation that injects a page object initialized
5InitialPage sets the page to be opened at the start of the test

Page Object

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)
	}
}
1We set the physical page which page object maps for
2FindBy maps a WebElement. If no name is provided, field name is used as id
3guardHttp ensures that all communication is done
dan

Avoid In
Mobile

Drive Mobile as a Browser

Arquillian Droidum
https://github.com/arquillian/arquillian-droidium

Droidum

Faults

Avoid In
Race Conditions and Uncommon Exceptions

Why Mocking?

Bytecode Manipulation

Byteman
http://byteman.jboss.org/

Byteman

Engage

Utility class

FileUtils.java
public void createFileWithContent(File filename, String content) throws IOException {
	BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
	bufferedWriter.write(content);
}

Test Uncommon Exception

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");
}
1set the class where code will be injected
2set the method where code will be injected
3set in which point the code will be injected
4code injected

Conclusions

github
github.com/lordofthejars/bjugbank

Alex Soto