Testing Spring
Applications
Riyadh Java Meetup
Mohammad Hewedy
github.com/mhewedy
1
1
Agenda
● Unit Test: What and Why?
● Test Driven Development
● Test Patterns
● Test Doubles
● Testing Spring Applications
○ Unit Testing
○ Integration Testing and the TestContext Framework
○ Slice Testing in Spring boot
● Demo
2
What?
3
4
A unit test is a piece of code written by a developer that exercises a very small,
specific area of functionality of the code being tested.
Unit tests are performed to prove that a piece of code does what the
developer thinks it should do.
Pragmatic Unit Testing in Java with JUnit
5
Why?
6
● It will make your designs better.
● Reduce the amount of time you spend debugging.
● Give confidence in the code.
● Make sure code do what you want.
● Do what you want all of the Time.
● Make sure you can depend on code.
● Document your intent.
● Shows how to use your code.
● Avoid any collateral damage
7
“Whac-a-Mole” effect
https://www.bakadesuyo.com/2012/10/whac-a-mole-teach-ability-focus/
8
a.k.a. “Samir Ghanem” effect
9
Why not?
10
● It takes too much time to write the tests?
● It takes too long to run the tests?
● It’s not my job to test my code?
● I’m being paid to write code, not to write tests?
● I feel guilty about putting testers and QA staff out of work?
11
Test Pyramid
Unit Tests
Integration Tests
(Service Tests)
E2E Tests
(UI Tests)
Fast
Slow
12
Characteristics of Good Tests
● Automatic
● Thorough
● Repeatable
● Independent
● Professional
● Speed
13
Always, “Spring The Trap”
Jim Baird - Adventurer
14
How?
15
class MathTest(TestCase):
def test_add(self):
math = Math()
result = math.add(10, 20)
assert_equals(30, result)
def test_multiply(self):
math = Math()
result = math.multiply(10, 20)
assert_equals(200, result)
class Math:
def add(self, x, y):
return x + y
def multiply(self, x, y):
return x * y
16
Test Driven Development
Red/Green/Refactor
Write
a test
Make it
compile
Run to
see it
fails
Make it
run
(see next)
Remove
Duplicate
(Refactor)
17
How to “Make it run” (Geen Bar Pattern)
1. Fake it
2. Triangulate
3. Obvious implementation
18
Green Bar Pattern in Action
19
Test Patterns
● Arrange, Act, Assert.
● Arrange, Assert-not, Act, Assert.
● Given, When, Then.
● Setup, Exercise, Verify, Teardown.
20
// spock framework
def "test listToString"() {
given:
def list = Arrays.asList("HELLO", "WORLD")
when:
def listToString = Util.listToString(list)
then:
listToString == "HELLOnWORLDn"
}
https://github.com/mhewedy/spwrap/blob/master/src/test/groovy/spwrap/UtilTest.groovy
21
@Unroll
def "#testDB : using implicit ResultSet Mapper to map result set"() {
given:
setup(testDB)
when:
customerDao.createCustomer0(“Abdullah”, “Mohammad”)
def list = customerDao.listCustomers(list)
then:
1 == list.size()
with(list.get(0)){
“Abdullah” == firstName()
“Mohammad” == lastName()
}
cleanup:
cleanup(testDB)
where:
testDB << [HSQL, MYSQL, SQLServer, ORACLE]
}
22
https://github.com/mhewedy/spwrap/blob/master/src/test/groovy/spwrap/DAOIntTest.groovy
Test Doubles
23
interface Mailer {
Boolean send(String to, String subject);
}
// billing is SUT
billing.bill(cusomer);
// mailer is collaborator
mailer.send(cusomer.getEmail(), postBillHtmlTemplate);
Collaborator object vs SUT
24
Dummy
class DummyMailer implements Mailer {
Boolean send(String to, String subject) {
return null;
}
}
25
Stub
class SuccessfullySendingMailerStub implements Mailer {
Boolean send(String to, String subject) {
return true;
}
}
26
Spy
class SuccessfullySendingMailerSpy implements Mailer {
public int callCount;
public String recordedTo;
public String recordedSubject;
Boolean send(String to, String subject) {
this.callCount += 1;
this.recordedTo = to;
this.recordedSubject = subject;
return true;
}
}
27
Mock
class SuccessfullySendingMailerMock implements Mailer {
private int callCount;
private String recordedTo;
private String recordedSubject;
Boolean send(String to, String subject) {
this.callCount += 1;
this.recordedTo = to;
this.recordedSubject = subject;
return true;
}
boolean verify(String expectedTo, String expectedSubject) {
return callCount == 1 &&
this.recordedTo.equals(expectedTo) &&
this.recordedSubject.equals(expectedSubject);
}
} 28
Fake
class MailerFake implements Mailer {
Boolean send(String to, String subject) {
return SimulatorDataSource.getMailer()
.containsRecord(to, subject);
}
}
29
To Mock or not to Mock
● The Mockist vs The Classicist
● The Mini-integration Tests
● The ObjectMother
● SUT and Collaborator objects
● Ways to Test the service layer:
○ Mock the Repository layer.
○ Use in-memory database.
○ Use a real test database.
30
Testing Spring
Applications
31
Unit Testing
32
Mocks provided by spring
● Environment:
○ MockEnvironment and MockPropertySource
● JNDI
○ SimpleNamingContextBuilder
○ e.g. To bind an embedded datasource to a jndi name
● Servlet api
○ MockHttpServletRequest, MockHttpServletResponse, MockHttpSession, etc.
Exists in package: org.springframework.mock
33
Support Classes
● ReflectionTestUtils
● AopTestUtils
● ModelAndViewAssert
● JdbcTestUtils
34
Integration Testing
35
Integration Testing
● Slower than unit tests.
● Perform some integration testing without requiring application
deployment.
● Exists in package: org.springframework.test
36
Goals of Spring Integration Testing support
● Context Management and Caching
● Dependency Injection of Test Fixtures
● Transaction Management
● Easily executing SQL Scripts
37
Important Annotations
● @ContextConfiguration
● @ActiveProfiles
● @TestPropertySource
● @DirtiesContext
● @Commit & @Rollback
● @BeforeTransaction & @AfterTransaction
● @Sql & @SqlConfig
● @Autowired, @PersistenceContext, @Qualifier, etc.
● @IfProfileValue, @Timed, @Repeat (junit4)
● @SpringJUnitConfig, @EnabledIf, @DisabledIf (junit5)
38
@TestExecutionListeners
● Important component of TestContext framework
● Accepts implementations of TestExecutionListener interface
● Used to implement many of the features
● Example implementations include:
○ ServletTestExecutionListener
○ DependencyInjectionTestExecutionListener
○ DirtiesContextTestExecutionListener
○ TransactionalTestExecutionListener
○ SqlScriptsTestExecutionListener
39
Meta Annotations
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf( expression =
"#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Disabled on Mac OS")
public @interface DisabledOnMac {}
40
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration(classes = {GeoConfig.class})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevGeoTestConfig {}
41
TestContext
Framework
42
JUnit 5 how is different from JUnit4?
● JUnit 4 uses Runner and Rule (TestRule & MethodRule)
○ @RunWith & @Rule
○ Examples:
■ MockitoJUnitRunner
■ SpringRunner
■ MockitoRule
■ WireMockRule
● JUnit 5 uses Extension
○ @ExtendWith
○ Composable
○ Examples:
■ SpringExtension
■ MockitoExtension
43
Spring TestContext is Test-Framework Agnostic
TestContextManager is
the entry point for Test
frameworks (JUnit 4,
JUnit5, TestNG) to the
TestContext framework.
44
Overview
● Support the Integration Testing
goals of spring
● Exists in package:
org.springframework.test.
context
● Core Classes
○ TestContextManager
○ TestContext
○ TestExecutionListener
○ SmartContextLoader
○ TestContextBootstrapper
45
TestContextManager
46
public void beforeTestMethod(Object testInstance, Method testMethod) throws Exception {
String callbackName = "beforeTestMethod";
getTestContext().updateState(testInstance, testMethod, null);
for (TestExecutionListener testExecutionListener : getTestExecutionListeners())
{
try {
testExecutionListener.beforeTestMethod(getTestContext());
}catch (Throwable ex) {
handleBeforeException(ex, callbackName,
testExecutionListener,
testInstance, testMethod);
}
}
} 47
TestContextBootstrapper
● Used to provide the TestContextManager by TestContext and the list
of TestExecutionListener
● You can customize using @BootstrapWith
● @SpringBootTest is itself annotated with
@BootstrapWith(SpringBootTestContextBootstrapper.class)
48
TestExecutionListener
● By default the following listeners are registered:
○ ServletTestExecutionListener
○ DirtiesContextBeforeModesTestExecutionListener
○ DependencyInjectionTestExecutionListener
○ DirtiesContextTestExecutionListener
○ TransactionalTestExecutionListener
○ SqlScriptsTestExecutionListener
● You can customize using @TestExecutionListeners or using
SpringFactoriesLoader mechanism.
● beware not to override default configured listeners
49
Features of
TestContext
50
Context Management
● Provide Context Loading from XML, Groovy and Annotated Classes
Example:
@ContextConfiguration(locations={"/app-config.xml", "/test-
config.xml"})
● Provide Context Initialization using classes implementation
ApplicationContextInitializer
● Provide Caching for the loaded Context between tests
● Can evict the cache using @DirtiesContext
51
@RunWith(SpringRunner.class)
@ContextConfiguration
public class OrderServiceTest {
@Configuration
static class Config {
@Bean
public OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
return orderService;
}
}
@Autowired
private OrderService orderService;
@Test
public void testOrderService() {
// test the orderService
}
} 52
Dependency Injection
● Implemented using DependencyInjectionTestExecutionListener
● Can use setter injection or field injection
● JUnit 5 you can use constructor injection
● Uses @Autowired or @Inject
53
Transaction Management
● Implemented using TransactionalTestExecutionListener.
● PlatformTransactionManager should exist in ApplicationContext
(auto-configured in spring boot).
● Must declare Spring @Transactional on test class or method.
● Cause test method transactions to rolled-back by default.
● Before and After Test callbacks are included in the transaction.
● Differentiate between Spring-managed and application-managed
transactions vs test-managed transactions
54
Transaction Management
● Use @Commit to force commit the transaction
● Use @BeforeTransaction and @AfterTransaction to run out-of-
transaction (it runs before Before and after After)
● o.s.t.c.transaction.TestTransaction could be used to run
programmed transactions
55
@DataJpaTest
@Transactional
@RunWith(SpringRunner::class)
class AccountRepositoryTest {
@Autowired
lateinit var accountRepository: AccountRepository
@Autowired
lateinit var entityManager: TestEntityManager
@Test
fun `test check balance never returns null for a valid account`() {
// Arrange
val account = Account()
entityManager.persist(account)
entityManager.flush() // to avoid false positives
// Act
val balance = accountRepository.checkBalance(account.id)
// Assert
assertThat(balance).isEqualTo(0.0)
}
}
56
Executing SQL Scripts
● @Sql implemented using SqlScriptsTestExecutionListener which
delegates to ResourceDatabasePopulator
● ResourceDatabasePopulator also used by spring boot auto-
configurations ("spring.datasource.schema" and
"spring.datasource.data")
● ResourceDatabasePopulator delegates to
o.s.jdbc.datasource.init.ScriptUtils
57
@SpringJUnitConfig // Junit 5
@Sql("/test-schema.sql")
class DatabaseTests {
@Test
@Sql(scripts="/test-schema.sql", config=@SqlConfig(commentPrefix =
"`"))
@Sql("/test-user-data.sql")
void userTest {
// test code here
}
}
58
Spring MVC Test Framework
● Built on the Servlet API mock objects
● Does not use a running Servlet container
● Why not pure Unit test is not enough? Need to test request mapping,
conversion, validation, etc.
● Two ways to create MockMvc:
○ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
○ this.mockMvc = MockMvcBuilders.standaloneSetup(new
AccountController()).build();
59
@Autowired
private MockMvc mockMvc; // auto-configured by spring boot
@MockBean
private AccountService accountService;
@Test
public void testDepositInvalidInput() throws Exception {
// ....
this.mockMvc.perform(put("/api/v1/account/1/balance/deposit")
.content(jsonObject.toString())
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.key")
.value("input.invalid.amountDto.amount.NotNull"));
}
60
Client-Side REST Tests
// Arrange
RestTemplate restTemplate = new RestTemplate()
MockRestServiceServer server =
MockRestServiceServer.bindTo(restTemplate).build();
server.expect(times(1),
requestTo("/hotels/42")).andExpect(method(HttpMethod.GET))
.andRespond(withSuccess("{ "id" : "42", "name" : "Holiday Inn"}",
MediaType.APPLICATION_JSON));
// Act
HotelService hotelService = new HotelService(restTemplate);
Hotel hotel = hotelService.findById(4); //should call the /hotels/42 endpoint
// Assert
server.verify();
61
Features of
Spring boot
62
Helpful Tools
● spring-boot-starter-test brings:
○ JUnit
○ Spring-Test
○ AssertJ: A fluent assertion library.
○ Hamcrest: A library of matcher objects (also known as constraints or predicates).
○ Mockito: A Java mocking framework.
○ JSONassert: An assertion library for JSON.
○ JsonPath: XPath for JSON.
63
Overview
● Use @SpringBootTest instead of @ContextConfiguration.
● By default @SpringBootTest run in MOCK environment
● Search for configurations automatically, no need to configure it manually.
○ - It search for classes annotated by @SpringBootApplication or
@SpringBootConfiguration.
● you can use nested @TestConfiguration to customize the primary
configuration.
● Use @MockBean to define Mockito mocks for beans inside the
ApplicationContext (mocking in integration tests)
64
Overview
● Can do real end-to-end servlet test using TestRestTemplate or
WebTestClient with @SpringBootTest(webEnvironment =
WebEnvironment.RANDOM_PORT)
○ Provide assertions similar to MockMvc
○ Can use REST Assured api as well
65
Testing Slices
● Configured in spring-boot-test-autoconfigure
● Load part of the application configurations/components
● You cannot use multiple slices together, but can @...Test one and
@AutoConfigure... or @ImportAutoConfiguration the others.
● Loads the corresponding Auto Configurations and beans
○ Example, @WebMvcTests does not regular @Componenet classes
66
Testing Slices
● Testing Slices support:
○ @JsonTest
○ @WebMvcTest
○ @WebFluxTest
○ @DataJpaTest
○ @DataRedisTest
○ @RestClientTest
○ ...
67
Demo
68
git clone https://github.com/mhewedy/primitive-bank.git
Extras
69
Using kotlin to write Tests?
70
References
● Test-Driven Development by Example
● Pragmatic Unit Testing in Java with JUnit
● https://blog.cleancoder.com/uncle-bob/2014/05/14/TheLittleMocker.html
● https://www.martinfowler.com/bliki/TestPyramid.html
● https://martinfowler.com/articles/mocksArentStubs.html
● https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html
● https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-
testing.html
● https://www.petrikainulainen.net/programming/testing/writing-clean-tests-to-verify-or-
not-to-verify/
71

Testing Spring Applications

  • 1.
    Testing Spring Applications Riyadh JavaMeetup Mohammad Hewedy github.com/mhewedy 1 1
  • 2.
    Agenda ● Unit Test:What and Why? ● Test Driven Development ● Test Patterns ● Test Doubles ● Testing Spring Applications ○ Unit Testing ○ Integration Testing and the TestContext Framework ○ Slice Testing in Spring boot ● Demo 2
  • 3.
  • 4.
  • 5.
    A unit testis a piece of code written by a developer that exercises a very small, specific area of functionality of the code being tested. Unit tests are performed to prove that a piece of code does what the developer thinks it should do. Pragmatic Unit Testing in Java with JUnit 5
  • 6.
  • 7.
    ● It willmake your designs better. ● Reduce the amount of time you spend debugging. ● Give confidence in the code. ● Make sure code do what you want. ● Do what you want all of the Time. ● Make sure you can depend on code. ● Document your intent. ● Shows how to use your code. ● Avoid any collateral damage 7
  • 8.
  • 9.
  • 10.
  • 11.
    ● It takestoo much time to write the tests? ● It takes too long to run the tests? ● It’s not my job to test my code? ● I’m being paid to write code, not to write tests? ● I feel guilty about putting testers and QA staff out of work? 11
  • 12.
    Test Pyramid Unit Tests IntegrationTests (Service Tests) E2E Tests (UI Tests) Fast Slow 12
  • 13.
    Characteristics of GoodTests ● Automatic ● Thorough ● Repeatable ● Independent ● Professional ● Speed 13
  • 14.
    Always, “Spring TheTrap” Jim Baird - Adventurer 14
  • 15.
  • 16.
    class MathTest(TestCase): def test_add(self): math= Math() result = math.add(10, 20) assert_equals(30, result) def test_multiply(self): math = Math() result = math.multiply(10, 20) assert_equals(200, result) class Math: def add(self, x, y): return x + y def multiply(self, x, y): return x * y 16
  • 17.
    Test Driven Development Red/Green/Refactor Write atest Make it compile Run to see it fails Make it run (see next) Remove Duplicate (Refactor) 17
  • 18.
    How to “Makeit run” (Geen Bar Pattern) 1. Fake it 2. Triangulate 3. Obvious implementation 18
  • 19.
    Green Bar Patternin Action 19
  • 20.
    Test Patterns ● Arrange,Act, Assert. ● Arrange, Assert-not, Act, Assert. ● Given, When, Then. ● Setup, Exercise, Verify, Teardown. 20
  • 21.
    // spock framework def"test listToString"() { given: def list = Arrays.asList("HELLO", "WORLD") when: def listToString = Util.listToString(list) then: listToString == "HELLOnWORLDn" } https://github.com/mhewedy/spwrap/blob/master/src/test/groovy/spwrap/UtilTest.groovy 21
  • 22.
    @Unroll def "#testDB :using implicit ResultSet Mapper to map result set"() { given: setup(testDB) when: customerDao.createCustomer0(“Abdullah”, “Mohammad”) def list = customerDao.listCustomers(list) then: 1 == list.size() with(list.get(0)){ “Abdullah” == firstName() “Mohammad” == lastName() } cleanup: cleanup(testDB) where: testDB << [HSQL, MYSQL, SQLServer, ORACLE] } 22 https://github.com/mhewedy/spwrap/blob/master/src/test/groovy/spwrap/DAOIntTest.groovy
  • 23.
  • 24.
    interface Mailer { Booleansend(String to, String subject); } // billing is SUT billing.bill(cusomer); // mailer is collaborator mailer.send(cusomer.getEmail(), postBillHtmlTemplate); Collaborator object vs SUT 24
  • 25.
    Dummy class DummyMailer implementsMailer { Boolean send(String to, String subject) { return null; } } 25
  • 26.
    Stub class SuccessfullySendingMailerStub implementsMailer { Boolean send(String to, String subject) { return true; } } 26
  • 27.
    Spy class SuccessfullySendingMailerSpy implementsMailer { public int callCount; public String recordedTo; public String recordedSubject; Boolean send(String to, String subject) { this.callCount += 1; this.recordedTo = to; this.recordedSubject = subject; return true; } } 27
  • 28.
    Mock class SuccessfullySendingMailerMock implementsMailer { private int callCount; private String recordedTo; private String recordedSubject; Boolean send(String to, String subject) { this.callCount += 1; this.recordedTo = to; this.recordedSubject = subject; return true; } boolean verify(String expectedTo, String expectedSubject) { return callCount == 1 && this.recordedTo.equals(expectedTo) && this.recordedSubject.equals(expectedSubject); } } 28
  • 29.
    Fake class MailerFake implementsMailer { Boolean send(String to, String subject) { return SimulatorDataSource.getMailer() .containsRecord(to, subject); } } 29
  • 30.
    To Mock ornot to Mock ● The Mockist vs The Classicist ● The Mini-integration Tests ● The ObjectMother ● SUT and Collaborator objects ● Ways to Test the service layer: ○ Mock the Repository layer. ○ Use in-memory database. ○ Use a real test database. 30
  • 31.
  • 32.
  • 33.
    Mocks provided byspring ● Environment: ○ MockEnvironment and MockPropertySource ● JNDI ○ SimpleNamingContextBuilder ○ e.g. To bind an embedded datasource to a jndi name ● Servlet api ○ MockHttpServletRequest, MockHttpServletResponse, MockHttpSession, etc. Exists in package: org.springframework.mock 33
  • 34.
    Support Classes ● ReflectionTestUtils ●AopTestUtils ● ModelAndViewAssert ● JdbcTestUtils 34
  • 35.
  • 36.
    Integration Testing ● Slowerthan unit tests. ● Perform some integration testing without requiring application deployment. ● Exists in package: org.springframework.test 36
  • 37.
    Goals of SpringIntegration Testing support ● Context Management and Caching ● Dependency Injection of Test Fixtures ● Transaction Management ● Easily executing SQL Scripts 37
  • 38.
    Important Annotations ● @ContextConfiguration ●@ActiveProfiles ● @TestPropertySource ● @DirtiesContext ● @Commit & @Rollback ● @BeforeTransaction & @AfterTransaction ● @Sql & @SqlConfig ● @Autowired, @PersistenceContext, @Qualifier, etc. ● @IfProfileValue, @Timed, @Repeat (junit4) ● @SpringJUnitConfig, @EnabledIf, @DisabledIf (junit5) 38
  • 39.
    @TestExecutionListeners ● Important componentof TestContext framework ● Accepts implementations of TestExecutionListener interface ● Used to implement many of the features ● Example implementations include: ○ ServletTestExecutionListener ○ DependencyInjectionTestExecutionListener ○ DirtiesContextTestExecutionListener ○ TransactionalTestExecutionListener ○ SqlScriptsTestExecutionListener 39
  • 40.
    Meta Annotations @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @DisabledIf(expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", reason = "Disabled on Mac OS") public @interface DisabledOnMac {} 40
  • 41.
    @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @ContextConfiguration(classes ={GeoConfig.class}) @ActiveProfiles("dev") @Transactional public @interface TransactionalDevGeoTestConfig {} 41
  • 42.
  • 43.
    JUnit 5 howis different from JUnit4? ● JUnit 4 uses Runner and Rule (TestRule & MethodRule) ○ @RunWith & @Rule ○ Examples: ■ MockitoJUnitRunner ■ SpringRunner ■ MockitoRule ■ WireMockRule ● JUnit 5 uses Extension ○ @ExtendWith ○ Composable ○ Examples: ■ SpringExtension ■ MockitoExtension 43
  • 44.
    Spring TestContext isTest-Framework Agnostic TestContextManager is the entry point for Test frameworks (JUnit 4, JUnit5, TestNG) to the TestContext framework. 44
  • 45.
    Overview ● Support theIntegration Testing goals of spring ● Exists in package: org.springframework.test. context ● Core Classes ○ TestContextManager ○ TestContext ○ TestExecutionListener ○ SmartContextLoader ○ TestContextBootstrapper 45
  • 46.
  • 47.
    public void beforeTestMethod(ObjecttestInstance, Method testMethod) throws Exception { String callbackName = "beforeTestMethod"; getTestContext().updateState(testInstance, testMethod, null); for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) { try { testExecutionListener.beforeTestMethod(getTestContext()); }catch (Throwable ex) { handleBeforeException(ex, callbackName, testExecutionListener, testInstance, testMethod); } } } 47
  • 48.
    TestContextBootstrapper ● Used toprovide the TestContextManager by TestContext and the list of TestExecutionListener ● You can customize using @BootstrapWith ● @SpringBootTest is itself annotated with @BootstrapWith(SpringBootTestContextBootstrapper.class) 48
  • 49.
    TestExecutionListener ● By defaultthe following listeners are registered: ○ ServletTestExecutionListener ○ DirtiesContextBeforeModesTestExecutionListener ○ DependencyInjectionTestExecutionListener ○ DirtiesContextTestExecutionListener ○ TransactionalTestExecutionListener ○ SqlScriptsTestExecutionListener ● You can customize using @TestExecutionListeners or using SpringFactoriesLoader mechanism. ● beware not to override default configured listeners 49
  • 50.
  • 51.
    Context Management ● ProvideContext Loading from XML, Groovy and Annotated Classes Example: @ContextConfiguration(locations={"/app-config.xml", "/test- config.xml"}) ● Provide Context Initialization using classes implementation ApplicationContextInitializer ● Provide Caching for the loaded Context between tests ● Can evict the cache using @DirtiesContext 51
  • 52.
    @RunWith(SpringRunner.class) @ContextConfiguration public class OrderServiceTest{ @Configuration static class Config { @Bean public OrderService orderService() { OrderService orderService = new OrderServiceImpl(); return orderService; } } @Autowired private OrderService orderService; @Test public void testOrderService() { // test the orderService } } 52
  • 53.
    Dependency Injection ● Implementedusing DependencyInjectionTestExecutionListener ● Can use setter injection or field injection ● JUnit 5 you can use constructor injection ● Uses @Autowired or @Inject 53
  • 54.
    Transaction Management ● Implementedusing TransactionalTestExecutionListener. ● PlatformTransactionManager should exist in ApplicationContext (auto-configured in spring boot). ● Must declare Spring @Transactional on test class or method. ● Cause test method transactions to rolled-back by default. ● Before and After Test callbacks are included in the transaction. ● Differentiate between Spring-managed and application-managed transactions vs test-managed transactions 54
  • 55.
    Transaction Management ● Use@Commit to force commit the transaction ● Use @BeforeTransaction and @AfterTransaction to run out-of- transaction (it runs before Before and after After) ● o.s.t.c.transaction.TestTransaction could be used to run programmed transactions 55
  • 56.
    @DataJpaTest @Transactional @RunWith(SpringRunner::class) class AccountRepositoryTest { @Autowired lateinitvar accountRepository: AccountRepository @Autowired lateinit var entityManager: TestEntityManager @Test fun `test check balance never returns null for a valid account`() { // Arrange val account = Account() entityManager.persist(account) entityManager.flush() // to avoid false positives // Act val balance = accountRepository.checkBalance(account.id) // Assert assertThat(balance).isEqualTo(0.0) } } 56
  • 57.
    Executing SQL Scripts ●@Sql implemented using SqlScriptsTestExecutionListener which delegates to ResourceDatabasePopulator ● ResourceDatabasePopulator also used by spring boot auto- configurations ("spring.datasource.schema" and "spring.datasource.data") ● ResourceDatabasePopulator delegates to o.s.jdbc.datasource.init.ScriptUtils 57
  • 58.
    @SpringJUnitConfig // Junit5 @Sql("/test-schema.sql") class DatabaseTests { @Test @Sql(scripts="/test-schema.sql", config=@SqlConfig(commentPrefix = "`")) @Sql("/test-user-data.sql") void userTest { // test code here } } 58
  • 59.
    Spring MVC TestFramework ● Built on the Servlet API mock objects ● Does not use a running Servlet container ● Why not pure Unit test is not enough? Need to test request mapping, conversion, validation, etc. ● Two ways to create MockMvc: ○ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); ○ this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build(); 59
  • 60.
    @Autowired private MockMvc mockMvc;// auto-configured by spring boot @MockBean private AccountService accountService; @Test public void testDepositInvalidInput() throws Exception { // .... this.mockMvc.perform(put("/api/v1/account/1/balance/deposit") .content(jsonObject.toString()) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andDo(print()) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.key") .value("input.invalid.amountDto.amount.NotNull")); } 60
  • 61.
    Client-Side REST Tests //Arrange RestTemplate restTemplate = new RestTemplate() MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build(); server.expect(times(1), requestTo("/hotels/42")).andExpect(method(HttpMethod.GET)) .andRespond(withSuccess("{ "id" : "42", "name" : "Holiday Inn"}", MediaType.APPLICATION_JSON)); // Act HotelService hotelService = new HotelService(restTemplate); Hotel hotel = hotelService.findById(4); //should call the /hotels/42 endpoint // Assert server.verify(); 61
  • 62.
  • 63.
    Helpful Tools ● spring-boot-starter-testbrings: ○ JUnit ○ Spring-Test ○ AssertJ: A fluent assertion library. ○ Hamcrest: A library of matcher objects (also known as constraints or predicates). ○ Mockito: A Java mocking framework. ○ JSONassert: An assertion library for JSON. ○ JsonPath: XPath for JSON. 63
  • 64.
    Overview ● Use @SpringBootTestinstead of @ContextConfiguration. ● By default @SpringBootTest run in MOCK environment ● Search for configurations automatically, no need to configure it manually. ○ - It search for classes annotated by @SpringBootApplication or @SpringBootConfiguration. ● you can use nested @TestConfiguration to customize the primary configuration. ● Use @MockBean to define Mockito mocks for beans inside the ApplicationContext (mocking in integration tests) 64
  • 65.
    Overview ● Can doreal end-to-end servlet test using TestRestTemplate or WebTestClient with @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) ○ Provide assertions similar to MockMvc ○ Can use REST Assured api as well 65
  • 66.
    Testing Slices ● Configuredin spring-boot-test-autoconfigure ● Load part of the application configurations/components ● You cannot use multiple slices together, but can @...Test one and @AutoConfigure... or @ImportAutoConfiguration the others. ● Loads the corresponding Auto Configurations and beans ○ Example, @WebMvcTests does not regular @Componenet classes 66
  • 67.
    Testing Slices ● TestingSlices support: ○ @JsonTest ○ @WebMvcTest ○ @WebFluxTest ○ @DataJpaTest ○ @DataRedisTest ○ @RestClientTest ○ ... 67
  • 68.
  • 69.
  • 70.
    Using kotlin towrite Tests? 70
  • 71.
    References ● Test-Driven Developmentby Example ● Pragmatic Unit Testing in Java with JUnit ● https://blog.cleancoder.com/uncle-bob/2014/05/14/TheLittleMocker.html ● https://www.martinfowler.com/bliki/TestPyramid.html ● https://martinfowler.com/articles/mocksArentStubs.html ● https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html ● https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features- testing.html ● https://www.petrikainulainen.net/programming/testing/writing-clean-tests-to-verify-or- not-to-verify/ 71

Editor's Notes

  • #14 Automatic: In Invoking the tests In Checking the results That the test must determine for itself whether it passed or failed. This idea of having the tests run by themselves and check themselves is critical, because it means that you don’t have to think about it—it just happens as part of the project Throught: Test everything that’s likely break based on the needs of your project Repeatable: independent of the environment run over and over again, in any order, and produce the same results. Independent: testing one thing at a time independent from the environment and each other Independent also means that no test relies on any other test Professional: written and maintained to the same professional standards as your production code code reuse.
  • #15 cause the production code to exhibit the very bug you’re trying to detect, and verify that the test fails as expected. Steps when fix a bug: 1. Identify the bug. 2. Write a test that fails, to prove the bug exists. 3. Fix the code such that the test now passes. 4. Verify that all tests still pass (i.e., you didn’t break anything else as a result of the fix
  • #39 - @ContextConfiguration <<inheritable>> <<load additional test configuration, ApplicationContextInitializer or configure ContextLoader>> - @ActiveProfiles <<inheritable>> - @TestPropertySource <<class level>> - @TestExecutionListeners: TestExecutionListener: beforeTestClass prepareTestInstance beforeTestExecution beforeTestMethod afterTestExecution afterTestMethod afterTestClass examples: ServletTestExecutionListener DependencyInjectionTestExecutionListener DirtiesContextTestExecutionListener TransactionalTestExecutionListener SqlScriptsTestExecutionListener - @DirtiesContext: DirtiesContextTestExecutionListener <<class&method level>> - @BootstrapWith TestContextBootstraper - @Commit & @Rollback: TransactionalTestExecutionListener - @BeforeTransaction & @AfterTransaction: TransactionalTestExecutionListener - @Sql & @SqlConfig: SqlScriptsTestExecutionListener - @Autowired, @PersistenceContext, @Qualifier, etc. : DependencyInjectionTestExecutionListener - @IfProfileValue, @Timed, @Repeat (junit4) - @SpringJUnitConfig, @EnabledIf, @DisabledIf (junit5)
  • #45 TestContextManager is independent of the Underlying Test framework It is being used by JUnit 5 by being used by SpringExtensions JUnit 5 Extension And used by JUnit 4 via Rules (SpringMethodRule) and Runners (Spring JUnit4ClassRunner) via the RunBeforeTestMethodCallbacks