Mocks, Stubs and Fakes. ¿What Else? .
Continuous Integration
Commit Stage
Some tools of unit can be used in integration
Unit tests
Individual units of source code, talk about what is units NOT common sense
FIRST
Fast, Isolated, Repeteable, Self-Validating, Timely
Test Double
Replace production object for testing purpose
Mocks
Pre-programmed with expectations
Behavior Verification
The Good and The Bad
Fast
Easy to write
Versatile
Mockito, EasyMock, jMock
Verbose
Reimplementing Business
No state
No executing full stack
Dependencies
testCompile ('org.mockito:mockito-all:1.10.19') {
exclude group: 'org.hamcrest'
}
Init
@RunWith(MockitoJUnitRunner.class)
public class Test {
@Mock EmailService email;
}
public class Test {
@Mock EmailService email;
@Before public void before() {
MockitoAnnotations.initMocks(this)
}
}
EmailService emailService = Mockito.mock(EmailService.class);
Example
Mockito.when(email.receiveBodyMessagesWithSubject("My Subject")).thenReturn("This is My Message")
System.out.println(email.receiveBodyMessagesWithSubject("Wrong subject"));
System.out.println(email.receiveBodyMessagesWithSubject("My Subject"));
System.out.println(email.receiveBodyMessagesWithSubject("My Subject"));
Example With Any
Mockito.when(email.receiveBodyMessagesWithSubject(Matchers.anyString())).thenReturn("This is My Message");
System.out.println(email.receiveBodyMessagesWithSubject("Subject"));
Example Verify
InvoiceService invoiceService = new InvoiceService(email);
invoiceService.send();
Mockito.verify(email, Mockito.times(1)).sendMessage(Matchers.anyString(), Matchers.anyString());
not verifying for example the number of emails sent. No state
Example Argument Captor
InvoiceService invoiceService = new InvoiceService(email);
invoiceService.send();
ArgumentCaptor argument = ArgumentCaptor.forClass(String.class);
Mockito.verify(email, Mockito.times(1)).sendMessage(argument.capture(), Matchers.anyString());
Assert.assertThat(argument.getValue(), CoreMatchers.startsWith("Invoice:"));
Example Spy
EmailService email = new EmailService();
EmailService spyEmail = Mockito.spy(email);
InvoiceService invoiceService = new InvoiceService(spyEmail);
invoiceService.send();
Mockito.verify(spyEmail, Mockito.times(1)).sendMessage(argument.capture(), Matchers.anyString());
Dependencies
testCompile ('org.powermock:powermock-module-junit4:1.6.4')
testCompile ('org.powermock:powermock-api-mockito:1.6.4')
Prepare for Mocking
@RunWith(PowerMockRunner.class)
@PrepareForTest(StaticClass.class)
public class MyTest {
}
Mock Static
PowerMockito.mockStatic(StaticClass.class);
Mockito.when(StaticClass.getValue()).thenReturn("Hello");
System.out.println(StaticClass.getValue());
PowerMockito.verifyStatic();
Mock Constructor
PowerMockito.whenNew(MyClass.class)
.withNoArguments()
.thenThrow(new IOException("error message"));
X x = new X();
x.createMyClass();
PowerMockito.verifyNew(MyClass.class).withNoArguments();
Private Method
Calculator calculator = PowerMockito.spy(new Calculator());
PowerMockito.doReturn(true).when(calculator, "isCalculated");
System.out.println(calculator.calculate());
PowerMockito.verifyPrivate(calculator).invoke("isCalculated");
Be Aware
All that Glitters is not Gold
It is so slow 10x
Probably shows you a problem with design
Example of System
if (System.currentTimeMillis() > pojo.getStartDate() + expiration) {
}
Java 8
java.time.Clock clock;
public void X() {
if (eventDate.isBefore(LocalDate.now(clock)) {
}
}
Java
TimeResolution clock;
public void X() {
if (clock.now() > pojo.getStartDate() + expiration) {
}
}
Stubs
Canned answers to calls
State Verification
Email Service
public class EmailServiceStub implements EmailService {
private List emails = new ArrayList<>();
public void sendMessage(String subject, String body) {
this.emails.add(new Email(subject, body));
}
public String receiveBodyMessagesWithSubject(String subject) {
// ...
}
}
The Good and The Bad
Fast
Easy to write
Versatile
Reimplementing Business
Logic is outside test
State
Wiremock
@Rule
public WireMockRule wireMockRule = new WireMockRule(8089);
stubFor(get(urlEqualTo("/planets/"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(Files.readAllBytes(configFile.toPath())))
);
client.target("http://localhost:8089/").path("planets/");
Fakes
Working implementations
With shortcuts
The Good and The Bad
Almost real
Test full stack
Slow
Learn New Tech
Different Behaviour
Mocking 2.0
Bytecode modification and implications
Fault injection
Byteman
testCompile ('org.jboss.byteman:byteman-bmunit:3.0.3') {
exclude group: 'org.testng'
}
Byteman
public WebWriter(String filename) {
this.filename = filename;
}
public PrintStream openFile() {
File file = new File(filename);
try {
FileOutputStream fos = new FileOutputStream(file);
PrintStream ps = new PrintStream(fos, true);
return ps;
} catch (FileNotFoundException e) {
System.out.println("Unable to open file " + file.getName());
System.out.println(e);
return null;
}
}
Byteman
@RunWith(org.jboss.byteman.contrib.bmunit.BMUnitRunner.class)
public class Test {
@Test
@BMRule(name = "handle file not found",
targetClass = "java.io.FileOutputStream",
targetMethod = "(File)",
action = "throw new FileNotFoundException( \"Ha ha Byteman fooled you again!\" )"
)
public void handleFileNotFound() throws FileNotFoundException {
System.out.println("-------- handleFileNotFound ---------");
WebWriter writer = new WebWriter("foo.html", "Andrew");
PrintStream ps = writer.openFile();
Assert.assertTrue(ps == null);
System.out.println("-------- handleFileNotFound ---------\n");
}
}
Slow tests are important in Commit stage as well (Happy)
Continuous Integration As Code
Pipeline Plugin
Jenkinsfile (1/2)
stage 'compileAndUnit'
node {
git branch: 'master', url: 'https://github.com/lordofthejars/starwars.git'
gradle 'clean test'
stash excludes: 'build/', includes: '**', name: 'source'
stash includes: 'build/jacoco/*.exec', name: 'unitCodeCoverage'
step([$class: 'JUnitResultArchiver', testResults: '**/build/test-results/*.xml'])
}
stage 'codeQuality'
parallel 'pmd' : {
node {
unstash 'source'
gradle 'pmdMain'
step([$class: 'PmdPublisher', pattern: 'build/reports/pmd/*.xml'])
}
}, 'jacoco': {
node {
unstash 'source'
unstash 'unitCodeCoverage'
gradle 'jacocoTestReport'
}
}
Jenkinsfile (2/2)
stage 'assemble-binaries'
node('linux') {
unstash 'source'
withEnv(["SOURCE_BUILD_NUMBER=${env.BUILD_NUMBER}"]) {
gradle 'assemble'
}
}
input message: "Deploy Application to QA ?"
stage name: 'Deploy to QA', concurrency: 1
node {
echo "Application Deployed to QA."
}
Let's wind down
Only first taste go to webpage
What You have Seen
Unit tests should be FIRST
Mock, Stubs and Fakes
PowerMock as Design Warning
Docker as Testing Tool
Jenkins is not static anymore
Fast is the parameter (10 minutes)