LogoLogo
diffblue.comFree trial
  • Discover Diffblue Cover
  • Get Started
    • What is Diffblue Cover?
    • Get started
      • Free trial
      • Get started - Cover Plugin
      • Get started - Cover CLI
      • Get started - Cover Pipeline
      • Get started - Cover Reports
    • Specs & Reqs
    • Reference Deployments
    • Licensing
      • Online license activation
      • Offline license activation
      • Diffblue License Manager
    • Update Cover
    • FAQs
    • Diffblue Learning
      • Get started
        • Just the basics
        • Free trial
        • Cover Plugin (IDE)
        • Cover CLI (Command Line)
      • Developer
        • Unit tests (IDE)
        • Unit tests (CLI)
        • Test coverage
      • DevOps
        • GitHub
        • GitLab
        • Other CI
      • Administrator
        • Admin - IntelliJ
        • Admin - CLI
        • Admin - Reports
      • Test coverage
        • Developer
        • Senior developer
        • Cover Reports Administrator
  • EVALUATION & ONBOARDING
    • Proof of Value
    • Jumpstart
      • Prerequisites for onboarding
      • Phase 1: Up and running
        • Module 1: Create your Cover unit test baseline
        • Module 2: Cover Pipeline CI integration
      • Phase 2: Developer productivity
        • Module 3: Getting started using Cover
        • Module 4: Introduction to Cover Reports
      • Phase 3: Advanced topics
        • Module 5: Speed up your test execution
        • Module 6: Getting more from Cover
  • Features
    • Cover Plugin
      • Writing tests
        • Gutter icons
        • Menu options
        • Run configurations
        • Cover Plugin tool window
        • Test Review
        • Test examples
        • Creating partial tests
        • Creating skeleton tests
        • Covering all enum values
        • Test insertion order
        • Diffblue Sandbox
        • Environment Check Cache
      • Project configuration
        • General dependencies
        • Test framework dependencies
      • Cover Plugin settings
        • Test Naming
        • Test Formatting
        • Spring configuration options
        • Method Annotations
        • Test Directory
        • Reset Cover Plugin settings
      • Cover Plugin admin
        • Core Maintenance
        • Cover Plugin toolbar menu
        • Cover Plugin status bar widget
        • Telemetry
        • Memory management
        • Using SonarQube with Cover Plugin
        • Log files
        • Troubleshooting
    • Cover CLI
      • Writing tests
        • Command summary
        • Test examples
        • Creating partial tests
        • Customizing test inputs
        • Customizing test setup
        • Test naming
        • Test formatting
        • Test insertion order
        • Patch files
        • Diffblue Sandbox
        • Operational behaviors
        • Test validation
      • Project configuration
        • Preflight checks
        • General dependencies
        • Test framework dependencies
        • Compiling your project successfully
          • Building a Maven project
          • Building a Gradle project
        • Configuring Cover to work with your project's build system
          • Quick guide
          • Configuration file in detail
          • Getting Started with Ant Projects
          • Default configuration
        • Mocking using Mockito
        • Spring profiles
        • Runtime environment
      • Commands & Arguments
        • Environment configuration for CI
        • Packages, classes, and methods
        • Quoting command line arguments
        • Argument files
      • Cover CLI admin
        • Core Maintenance
        • Telemetry
        • Memory management
        • Using Cover CLI in Eclipse
        • Using SonarQube with Cover CLI
        • Log files
        • Troubleshooting
      • Environment Configuration
    • Cover Pipeline
      • Cover Pipeline for CI
        • Quick Start - General
        • Quick Start - Jenkins
        • Quick Start - Azure Pipelines
        • Quick Start - AWS Codebuild
        • Integrating Diffblue Cover into CI on pull requests
        • Installation - VMs or CI run
      • Cover Pipeline for GitLab
        • Introduction
        • Installation and initial setup
        • GitLab workflow
        • Configuration
        • Troubleshooting
        • Related topics
      • Cover Pipeline for GitHub
        • Introduction
        • Installation and initial setup
        • GitHub workflow
        • Configuration
        • Troubleshooting
        • Related topics
    • Cover Reports
      • Cover Reports Contributor
        • Java project config (JaCoCo)
        • Generate and upload reports bundles
        • Authenticated uploads
      • Cover Reports User
        • Navigation
        • Dashboards
        • Telemetry data
        • Export API
        • Considerations
      • Cover Reports Administrator
        • Install and update Cover Reports
        • Configuration options
        • Database backup
        • SSO with Cover Reports
        • Uninstall Cover Reports
    • Cover Optimize
      • Get started - Cover Optimize
      • Cover Optimize & Gradle
      • Cover Optimize & Maven
      • Patch files
    • Cover Refactor
      • Get started - Cover Refactor
    • Cover Annotations
      • Mocking Annotations
      • Custom Input Annotations
      • Interesting Value Annotations
    • Output Codes
      • E - Environment Codes
      • L - License Codes
      • R - Reason Codes
      • T - Testability Codes
      • V - Validation Codes
      • Working with output codes
        • Working with code E020
        • Working with codes E057 to E065
        • Working with code E085
        • Working with code R005
        • Working with code R006
        • Working with code R008
        • Working with code R011
        • Working with code R012
        • Working with code R013
        • Working with code R026
        • Working with code R031
        • Working with code V003
    • Tutorials
      • How to measure test quality
      • How to improve code coverage
      • How to test a new feature
      • How to find regressions
      • How to use Diffblue Cover in test driven development (TDD)
      • How to write tests for Kotlin projects
      • Examples of tests created by Diffblue Cover
      • Best practices for testing private methods
  • Updates & Upgrades
    • Update Cover
    • Cover Editions
    • What's new
    • Release archive
      • 2025-05-01
      • 2025-04-02
      • 2025-04-01
      • 2025-03-02
      • 2025-03-01
      • 2025-02-02
      • 2025-02-01
      • 2025-01-02
      • 2025-01-01
      • 2024-12-02
      • 2024-12-01
      • 2024-11-02
      • 2024-11-01
      • 2024-10-02
      • 2024-10-01
      • 2024-09-02
      • 2024-09-01
      • 2024-08-02
      • 2024-08-01
      • 2024-07-04
      • 2024-07-03
      • 2024-07-01
      • 2024-06-02
      • 2024-06-01
      • 2024-05-02
      • 2024-05-01
      • 2024-04-02
      • 2024-04-01
      • 2024-03-02
      • 2024-03-01
      • 2024-02-02
      • 2024-02-01
      • 2024-01-02
      • 2024-01-01
      • 2023-12-02
      • 2023-12-01
      • 2023-11-03
      • 2023-11-02
      • 2023-11-01
      • 2023-10-02
      • 2023-10-01
      • 2023-09-02
      • 2023-09-01
      • 2023-08-02
      • 2023-08-01
      • 2023-07-03
      • 2023-07-02
      • 2023-07-01
      • 2023-06-02
      • 2023-06-01
      • 2023-05-02
      • 2023-05-01
      • 2023-04-02
      • 2023-04-01
      • 2023-03-02
      • 2023-03-01
      • 2023-02-02
      • 2023-02-01
      • 2023-01-02
      • 2023-01-01
      • 2022-12-02
      • 2022-12-01
      • 2022-11-02
      • 2022-11-01
      • 2022-10-03
      • 2022-10-02
      • 2022-10-01
      • 2022-09-02
      • 2022-09-01
      • 2022-08-05
      • 2022-08-03
      • 2022-08-02
      • 2022-08-01
      • 2022-07-02
      • 2022-07-01
      • 2022-06-02
      • 2022-06-01
      • 2022-05-02
      • 2022-05-01
      • 2022-04-02
      • 2022-04-01
      • 2022-03-02
      • 2022-03-01
      • 2022-02-02
      • 2022-02-01
      • 2022-01-02
      • 2022-01-01
      • 2021-06-02
      • 2021-02-01
  • Legal
    • Diffblue Legal
      • Diffblue End User License Agreement (EULA)
      • Cover Third Party Notices and/or Licenses
      • Privacy Notice
Powered by GitBook
On this page

Was this helpful?

Export as PDF
  1. Features
  2. Tutorials

How to use Diffblue Cover in test driven development (TDD)

In this tutorial we will look at how to use Cover Plugin for IntelliJ when using test-driven development (TDD) for implementing a new feature.

To follow this part of the tutorial on your own, please do the following steps:

  • git clone https://github.com/Diffblue-benchmarks/Spring-petclinic

  • cd Spring-petclinic

  • git checkout a-test-tdd

  • Open the project in IntelliJ.

  • Navigate to the OwnerController class.

1. We are going to add a new feature to the OwnerController class (that is specified as follows):

   /**
   * @GetMapping("/owners") public String processFindForm(Owner owner, ...)
   * Look up the owner in the database by the given last name.
   * If a single owner is found, redirect to /owners/{ownerId}.
   * If several owners are found, allow selection of an owner in owners/ownersList.
   */

TDD requires us to write the tests before implementing the functionality. So, we start by manually implementing tests for this new processFindForm method.

2. We first have to set up the test class wiring the required beans:

@WebMvcTest(controllers = {OwnerController.class})
@ExtendWith(SpringExtension.class)
public class OwnerControllerTest {

  @Autowired
  private MockMvc mockMvc;

  @MockBean(name = "ownerRepository")
  private OwnerRepository ownerRepository;

  @MockBean(name = "visitRepository")
  private VisitRepository visitRepository;
...

3. We then add a test method for the case where a single owner is found and we redirect to that owner:

   …
@Test
public void testProcessFindForm_singleOwner() throws Exception {
  Owner owner = new Owner();
  owner.setLastName("Doe");
  owner.setId(1);
  owner.setCity("Oxford");
  owner.setAddress("42 Main St");
  owner.setFirstName("Jane");
  owner.setTelephone("4105551212");
  when(this.ownerRepository.findByLastName(or(isA(String.class), isNull())))
     .thenReturn(Collections.singletonList(owner));
  this.mockMvc.perform(get("/owners").param("lastName", "Doe"))
     .andExpect(status().is3xxRedirection())
     .andExpect(view().name("redirect:/owners/1"));
}
…

4. We then write a test for the case where multiple owners are found and we display the list for selecting an owner:

…
@Test
public void testProcessFindForm_severalOwners() throws Exception {

  Owner owner1 = new Owner();
  owner1.setLastName("Doe");
  owner1.setId(1);
  owner1.setCity("Oxford");
  owner1.setAddress("42 Main St");
  owner1.setFirstName("Jane");
  owner1.setTelephone("4105551212");

  Owner owner2 = new Owner();
  owner2.setLastName("Doe");
  owner2.setId(1);
  owner2.setCity("Oxford");
  owner2.setAddress("1 High St");
  owner2.setFirstName("John");
  owner2.setTelephone("5121241055");

  when(this.ownerRepository.findByLastName(or(isA(String.class), isNull())))
     .thenReturn(Arrays.asList(owner1, owner2));

  this.mockMvc.perform(get("/owners").param("lastName", "Doe"))
     .andExpect(status().isOk())
     .andExpect(view().name("owners/ownersList"));
}
…

These tests should fail because we haven’t implemented the corresponding functionality yet.

5. The next step is to implement processFindForm:

public class OwnerController {
…
@GetMapping("/owners")
public String processFindForm(
    Owner owner, BindingResult result, Map<String, Object> model) {

  Collection<Owner> results = this.owners.findByLastName(owner.getLastName());
   if (results.size() == 1) {
     owner = results.iterator().next();
     return"redirect:/owners/" + owner.getId();
   } else {
     model.put("selections", results);
     return"owners/ownersList";
  }
}

6. We then run the manually written tests to check whether our implementation is correct. These tests succeed. So, are we finished? Let's use the Diffblue Cover plugin for IntelliJ to see what it produces for us.

7. We receive a couple of tests. Let’s review them:

public class OwnerControllerTest {
…
@Test
public void testProcessFindForm() throws Exception {

  when(this.ownerRepository.findByLastName(or(isA(String.class), isNull())))
     .thenReturn(new ArrayList<Owner>());

  this.mockMvc.perform(get("/owners"))
     .andExpect(status().isOk());
     .andExpect(view().name("owners/ownersList"));
}

This first test shows us behaviour for the case where no owner is found. That’s a case we haven’t considered in the requirements and therefore missed it when we wrote our manual tests. Thus our implementation might also be incorrect in that situation.

8. We have to amend our requirements to make them complete, i.e. we need to define the behaviour for the case where no owner is found. In this case we don’t want to show the owner selection list, but instead redirect back to the find owners page and show an error:

   /**
   * @GetMapping("/owners") public String processFindForm(Owner owner, …)
   * If no owner is found, return to owners/findOwners.
   * Look up the owner in the database by the given last name.
   * If a single owner is found, redirect to /owners/{ownerId}.
   * If several owners are found, allow selection of an owner in owners/ownersList.
   */

9. We also need to modify the test to make it match with this additional requirement:

public class OwnerControllerTest {
…
@Test
public void testProcessFindForm_notFound() throws Exception {

  when(this.ownerRepository.findByLastName(or(isA(String.class), isNull())))
     .thenReturn(new ArrayList<Owner>());

  this.mockMvc.perform(get("/owners"))
     .andExpect(status().isOk());
     .andExpect(view().name("owners/findOwners"));
}

10. This test is now expected to fail because our implementation doesn’t consider this unforeseen requirement yet. Let’s implement it:

public class OwnerController {
…
@GetMapping("/owners")
public String processFindForm(
    Owner owner, BindingResult result, Map<String, Object> model) {

  Collection<Owner> results = this.owners.findByLastName(owner.getLastName());
  if (results.isEmpty()) {
    result.rejectValue("lastName", "notFound", "not found");
    return"owners/findOwners";
  } else if (results.size() == 1) {
     owner = results.iterator().next();
     return "redirect:/owners/" + owner.getId();
  } else {
     model.put("selections", results);
     return "owners/ownersList"**;
  }
}

11. The test should pass now, but we have one more test to review:

public class OwnerControllerTest {
…
@Test
public void testProcessFindForm1 throws Exception {
  Owner owner = new Owner();
  owner.setLastName("Doe");
  owner.setId(1);
  owner.setCity("Oxford");
  owner.setAddress("42 Main St");
  owner.setFirstName("Jane");
  owner.setTelephone("4105551212");

  when(this.ownerRepository.findByLastName(or(isA(String.class), isNull())))
     .thenReturn(Collections.singletonList(owner));

  this.mockMvc.perform(get("/owners"))
     .andExpect(status().is3xxRedirection())
     .andExpect(view().name("redirect:/owners/1"));
}

12. In this test we search for owners without actually specifying a last name. What should happen in this case? It’s not specified in our requirements. Again, we need to fill this gap in the requirements:

   /**
   * @GetMapping("/owners") public String processFindForm(Owner owner, …)
   * If no owner is found, return to owners/findOwners.
   * Look up the owner in the database by the given last name.
   * If a single owner is found, redirect to /owners/{ownerId}.
   * If several owners are found, allow selection of an owner in owners/ownersList.*
   * If no last name is given, allow selection among all owners.
   */

The above test is correct as there is only a single owner returned in our mock. We could add another test that returns multiple owners and redirects to owners/ownersList to cover that case too.

13. To complete the implementation, we make the following change:

public class OwnerController {
…
@GetMapping("/owners")
public String processFindForm(
    Owner owner, BindingResult result, Map<String, Object> model) {
  // allow parameterless GET request for /owners to return all records
  if (owner.getLastName() == null) {
    owner.setLastName(""); // empty string signifies broadest possible search
  }
  Collection<Owner> results = this.owners.findByLastName(owner.getLastName());
  if (results.isEmpty()) {
    result.rejectValue("lastName", "notFound", "not found");
    return "owners/findOwners";
  } else if (results.size() == 1) {
     owner = results.iterator().next();
     return "redirect:/owners/" + owner.getId();
  } else {
     model.put("selections", results);
     return "owners/ownersList";
  }
}

14. All the tests should pass now and cover the entire implementation.

The diagram below shows our journey through this tutorial:

Summary

In this use case, we started the TDD process as usual by analyzing the requirements and implementing the unit tests. Then we implemented the feature to make the unit tests pass. At this point we would usually stop and commit the code. The value add that Diffblue Cover Plugin for IntelliJ provides for us is that it gives us additional tests that might include cases that we haven’t considered in our initial requirements analysis. That means that Diffblue Cover helps us identify gaps in the requirements. We did that by reviewing the tests Diffblue Cover created for us, amending the requirements with the missing cases and adapting the unit tests to match these unforeseen requirements. We then completed the implementation to make the unit tests pass.

PreviousHow to find regressionsNextHow to write tests for Kotlin projects

Last updated 1 year ago

Was this helpful?