Testing Java Code in 2013

Beyond JUnit w/ Hamcrest, Mockito, Spring, and Spock!
{by Jeff Sheets}

These slides are up on GitHub!

jeffsheets/JavaTesting2013Slides

About Me

Objectives Assuming...

  • Spring MVC + JPA/Hibernate
  • Using Maven {}
  • Using Eclipse/STS/GGTS or RAD { }
  • We want tools newer than JUnit 3
  • Possibly use Groovy for tests (but not prod code)
  •  
  • Please ask questions!
  • Everyone in this room is an expert!

Why test your code?

  • Developers -> Code
  • Senior Developers -> Code and Test
  • Chuck Norris -> continuously deploys to prod with 100% test coverage -- in COBOL  

Really why test?

  • Obvious (you wouldn't be in this room otherwise)
  • Molds your testable coding style
  • Faster than redeploy-test-repeat
  • Coding with confidence
  • Continuous Deployment  

Keep It Simple

  • Do all of your tests pass?
  • Jenkins Server (in the cloud!)
  • Test Independence
  • Not too brittle

Sample App

Unit vs Integration vs Functional

  • unit: JUnit+Mockito or Spock
  • int: Spring Transactional and Wiring
  • func: Geb, Selenium, Cloud...
  • Others???
  • How to separate the types?

Separating Testing types - by Dir

  • src/java/main
  • src/java/test
  • src/java/integration ???
  • src/groovy/test ---> for spock
  • src/groovy/integration
  • Maven makes this very difficult!

Separating Testing types - by File

  • MyServiceTest.java
  • MyServiceIT.java //Maven failsafe plugin pattern
  • MyServiceSpec.groovy //Spock
  • MyServiceSpecIT.groovy //Spock + failsafe
  • Failsafe setup in Pom.xml

Traditional JUnit

CarRepositoryIT.java

//Setup your Eclipse Favorites for code suggestions
import static org.junit.Assert.assertEquals;

/* ... */

assertEquals(2013, result.getYear().intValue());
assertEquals("JunitMotors", result.getMake().getName());
assertEquals(make.getId(), result.getMake().getId());
assertEquals(car.getDescription(), result.getDescription());


Hamcrest

CarRepositoryIT.java

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

/* ... */

assertThat(result.getYear().intValue(), is(equalTo(2013)));
assertThat(result.getMake().getName(), is("JunitMotors"));
assertThat(result.getMake().getId(), is(make.getId()));
assertThat(result.getDescription(), is(car.getDescription()));

//containsString
//equalToIgnoringCase

Hamcrest Details

  • Readable assertions (actual before expected)
  • Hamcrest Docs
     
  • ProviderRepositoryIT.java

assertThat(result, not(nullValue()));
assertThat(result, hasSize(original.size() + 1));

//This requires overridden provider.equals method
assertThat(result, hasItem(provider));

Custom Matchers

  • Implement Matcher interface
  • matches() and describeTo() methods
  • Use static methods for readability
  • CustomMatchers.java and PropertiesIT.java

import static com.sheetsj.test.matchers.CustomMatchers.isStringInjected;

/* ... */

assertThat(anotherProperty, isStringInjected());


Fest


import static org.fest.assertions.Assertions.assertThat;

/* ... */

assertThat(demoUser.getEmail()).isEqualTo(userDetails.getUsername());
assertThat(demoUser.getPassword()).isEqualTo(userDetails.getPassword());
assertThat(hasAuthority(userDetails, demoUser.getRole()));


Checking Thrown Exceptions

JUnit 4 actually provides a BAD way

//Don't copy this!
@Test(expected=IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
    Object a = service.getAllResults().get(2);
    Object b = service.getAllForA(a).get(2);
}

The Better Way

Use ExpectedException @Rule (UserServiceTest.java)

@Rule
public ExpectedException thrown = ExpectedException.none();

public void shouldThrowExceptionWhenUserNotFound() {
  thrown.expect(UsernameNotFoundException.class);
  thrown.expectMessage("not found");  //NOTE: something funny here
  
  when(accountRepositoryMock.findByEmail("user@example.com")).thenReturn(null);
  
  userService.loadUserByUsername("user@example.com");
}

The Custom Way

  • BusinessValidationExceptionMatchers.java
  • Custom Hamcrest matchers for sourceField and arguments

public static void expect(String message, ExpectedException thrown)
{
  thrown.expect(BusinessValidationException.class);
  thrown.expectMessage(equalTo(message));
}

/** From UserServiceTest.java */
public void shouldThrowBusinessExceptionWhenUsernameTooShort() {
  //Uses custom expect matcher using equalTo() instead of containsString()
  expect("username", "username.too.short", thrown, "ab");
  
  userService.loadUserByUsername("ab");
}


Mockito Annotations

  • We've all used Mockito? JMock? Others?
  • Why? To isolate our Unit Tests!
  • Mock method return values
  • Verify mock methods were called
  • Spy to mock method on class-under-test

The Old Mockito Way

WorkItemServiceOldMockitoWayTest.java

private WorkItemRepository workItemRepository = mock(WorkItemRepository.class);
private WorkItemService service = new WorkItemService(workItemRepository);

/* .. inside a test method .. */
when(workItemRepository.findAll()).thenReturn(allWorkItems);

The New Mockito Way

WorkItemServiceTest.java

@RunWith(MockitoJUnitRunner.class)
public class WorkItemServiceTest {
  @Mock
  private WorkItemRepository workItemRepository;
  
  @InjectMocks
  private WorkItemService service = new WorkItemService();
}

Those Double-Crossing Spies

  • Spies are a code smell
  • But sometimes it is just easier (legacy)

@InjectMocks
@Spy
private WorkItemService service = new WorkItemService();

/* ... in test method ... */
doReturn(expectedList).when(service)
     .selectWorkItemsByMakeAndYear("bmw", "2008", allWorkItems);
     
Collection<WorkItem> results = service.findAllByMakeAndYear("bmw", "2008");

//Showing nested matchers in contains and sameInstance
assertThat(results, contains(sameInstance(expected)));


Spring Integration Tests

  • Are you testing your ORM/SQL?
  • {Stored Procs? Sorry!}
  • What about Spring Wiring?
  • {Especially any injected properties}

Transactional Tests

Auto-rollback from real database!
IntegrationTestBaseClass.java

@ContextConfiguration(loader=AnnotationConfigContextLoader.class, 
             classes={RootConfig.class, PersistenceConfig.class})
@ActiveProfiles("test")
public abstract class IntegrationTestBaseClass extends 
                  AbstractTransactionalJUnit4SpringContextTests {
                  
}

Transactional Tests 2

Test now talks to real DB
WorkItemRepositoryIT.java

public class WorkItemRepositoryIT extends IntegrationTestBaseClass {
  List<WorkItem> original = workItemRepository.findAll();
  
  Manufacturer make = manufacturerRepository.save(new Manufacturer("JUnitMake"));
  Car car = carRepository.save(new Car(2013, make, "JunitModel", "LT FWD 3.6L V6 DOHC 24V"));
  Provider provider = providerRepository.save(new Provider("Junit Tire Shop", "Shadow Lake"));
  
  WorkItem workItem = new WorkItem(car, new Date(), "Oil Change", provider, 18123L, 60.12, "Took 1:45 so got free oil change coupon");
  workItem = workItemRepository.save(workItem);
  
  List<WorkItem> result = workItemRepository.findAll();
}

Mad Props

  • Integration Test our Properties too
  • Especially if coming from database table
  • Two styles: 1) All props in PropertiesIT.java

public class PropertiesIT extends IntegrationTestBaseClass {

  @Value("${another.property}")
  private String anotherProperty;
  
  @Test
  public void testAnotherProperty() {
    assertThat(anotherProperty, isStringInjected());
  }
}

Mad Props 2

  • 2) Test on injected class
  • {Better but needs protected getter}

public class UserServiceIT extends IntegrationTestBaseClass {
  @Autowired
  private UserService userService;
  
  @Test
  public void testPropertyIsInjected() {
    assertThat(userService.getAnotherProperty(), isStringInjected());
  }
}


Spock Testing Framework

  • "The Enterprise Ready Specification Framework"
  • Yes, Spock is Groovy
  • But tests Java classes fine (or better)
  • Read the Intro Docs
  • IANASE (I Am Not A Spock Expert)

Spock Setup


<properties>
  <spock.version>0.7-groovy-2.0</spock.version>
  <groovy.version>2.0.7</groovy.version>
</properties>

Spock Setup 2

  • 2) Build Path for src/test/groovy
  • 3) Output Folder for src/test/groovy
  • 4) Profit???

Spock Setup Gotcha's



  
    
      
        org.codehaus.gmaven
        gmaven-plugin
        [1.5,)
        
          testCompile
          
        
      
       
        
      
    
  



Spock Test Code

Basic Layout

class WorkItemServiceSpecTest extends Specification {
  def service = new WorkItemService()
  def "getYearAsString works for simple case"() {
    given: 'a normal date'
    def date = Date.parse("MM/dd/yyyy", "08/01/2013")
    
    when: 'getYearAsString is called'
    def result = service.getYearAsString(date)
    
    then: 'year is correct on result'
    result == "2013"
  }
}

Expect: for functional code


def "getYearAsString works for simple case using expect block"() {
  given: 'a normal date'
  def date = Date.parse("MM/dd/yyyy", "08/01/2013")
  
  expect: 'getYearAsString is correct when called'
  service.getYearAsString(date) == "2013"
}

Spock Mocks Setup


class WorkItemServiceSpecTest extends Specification {
  def service = new WorkItemService()
  def workItemRepository = Mock(WorkItemRepository)
  def setup() {
    service.workItemRepository = workItemRepository
  }
}

Spock Mocks - Simple


def "findAllByMakeAndYear works with mocks"() {
  given: 'a bunch of work items'
  List<WorkItem> allWorkItems = buildAllWorkItems()
  
  when: 'findAllByMakeAndYear is called'
  def results = service.findAllByMakeAndYear('ford', '2013')
  
  then: 'results are correct'
  1 * workItemRepository.findAll() >> allWorkItems
  results.containsAll allWorkItems[2,4]
}

Spock Mocks - Wildcard _


/** not a good pattern. just showing it is possible */
def "findAllByMakeAndYear with many tests at once"() {
  given: 'a bunch of work items'
  List<WorkItem> allWorkItems = buildAllWorkItems()
  _ * workItemRepository.findAll() >> allWorkItems
  
  expect: 'findAllByMakeAndYear is called and has correct results'
  service.findAllByMakeAndYear('ford', '2013').containsAll(
     allWorkItems[2,4])
  service.findAllByMakeAndYear('ford', '2012').containsAll(
     allWorkItems[0..1])
  service.findAllByMakeAndYear('chevy', '2013').empty
  !service.findAllByMakeAndYear('ford', '2014')
}

Spock - Multiple when/then


def "findAllByMakeAndYear with many tests at once but multiple blocks"() {
  given: 'a bunch of work items'
  List allWorkItems = buildAllWorkItems()
  
  when: 'findAllByMakeAndYear is called for Ford and 2013'
  def results = service.findAllByMakeAndYear('ford', '2013')
  
  then: 'results are correct'
  1 * workItemRepository.findAll() >> allWorkItems
  results.containsAll allWorkItems[2,4]
  
  when: 'findAllByMakeAndYear is called for Ford and 2012'
  results = service.findAllByMakeAndYear('ford', '2012')
  
  then: 'results are correct'
  1 * workItemRepository.findAll() >> allWorkItems
  results.containsAll allWorkItems[0..1]
}

Spock @Unroll Magic FTW!

  • Wow. Killer Feature!    
  • Parameterized Tests (anyone doing JUnit style?)

@Unroll
def "getYearAsString with #inputDate returns #result"
                                (String inputDate, String result) {
expect: 'getYearAsString is correct when called'
  service.getYearAsString(
              Date.parse("MM/dd/yyyy", inputDate)) == result
              
  where:
  inputDate    | result
  "01/01/1999" | "1999"
  "02/01/2000" | "2000"
  "03/01/199"  | "199"
  '03/01/2013' | '2013'	
}

Spock old() method

  • "Wait, WTF is going on there? That’s voodoo!" -- Rob Fletcher

def "findAll workItems and Save showing how the old method works"() {
  //...
  
  when: 'workItem saved, and results retrieved'
  WorkItem workItem = new WorkItem(car, new Date(), "Oil Change", provider, 18123L, 60.12, "Took 1:45 so got free oil change coupon");
  workItem = workItemRepository.save(workItem);
  List<WorkItem> result = workItemRepository.findAll();
  
  then: 'size was incremented'
  result.size() == old(workItemRepository.findAll()).size() + 1
}

Spock Wires into Spring

  • Needs spock-spring plugin
  • Just use @Autowired
  • Setup Transaction rollbacks
  • WorkItemRepositorySpecIT.groovy

@ContextConfiguration(loader=AnnotationConfigContextLoader, 
                      classes=[RootConfig, PersistenceConfig])
@Transactional
abstract class IntegrationSpecBaseClass extends Specification {
}

Spock + Hamcrest?

  Yes it can be done (that() and expect())

  expect: 'findAllByMakeAndYear returns correct results'
  that result1, containsInAnyOrder(allWorkItems[2], allWorkItems[4])
  that result2, containsInAnyOrder(allWorkItems[0], allWorkItems[1])
  that result3, empty()
  that result4, empty()

Spock + Hamcrest? 2

  But groovy often easier

expect: 'findAllByMakeAndYear returns correct results'
result1.containsAll allWorkItems[2,4]
result2.containsAll(allWorkItems[0..1])
result3.empty
!result4

Spock Miscellaneous

  • thrown() and notThrown()
  • setup() and cleanup()
  • setupSpec() and cleanupSpec()
  • @Ignore, @IgnoreRest, @Timeout, @FailsWith
  • where: using simple arrays instead of tables
  • SpockBasics

Spock vs JUnit+Mockito+Hamcrest

  • Spock gives you power of Groovy
  • I prefer readability of Spock Specs
  • Opinions? Thoughts? Spock Questions?

Testing REST Clients


public void testGetMessage() {
  mockServer.expect(requestTo("http://google.com"))
    .andExpect(method(HttpMethod.GET))
    .andRespond(withSuccess("resultSuccess", MediaType.TEXT_PLAIN));
    
  String result = simpleRestService.getMessage();
  
  mockServer.verify();
  assertThat(result, allOf(containsString("SUCCESS"), 
                           containsString("resultSuccess")));
}


Testing Mail Senders

  • MockMailSender for Spring JavaMailSender
  • Mocks it out so no mail is sent
  • Other tools spinning up real mail servers
  • Described well in this Blog Post
  • See MockMailSender.java

Conclusion

References

Questions

Java Trends in 2013

  • Cloud PaaS (Heroku & Cloudfoundry/Appfog)
  • Continuous Deployment (Cloudbees & drone.io)
  • Online IDEs (codenvy and cloud9)
  • Dropwizard / Spring Boot / No-Servlet?
  • Akka async promises in Java/Scala
  • Java 8 Functional Style (next year)