• 2024/09/20 - corrected ToolRentalProperties/BoatRentalProperties typo on ConfigurationProperties assignment

The following three areas (Config, Logging, and Testing) map out the different portions of "Assignment 1". It is broken up to provide some focus.

  • Each of the areas (1a Config, 1b Logging, and 1c Testing) are separate but are to be turned in together, under a single root project tree. There is no relationship between the classes used in the three areas — even if they have the same name. Treat them as separate.

  • Each of the areas are further broken down into parts. The parts of the Config area are separate. Treat them that way by working in separate module trees (under a common grandparent). The individual parts for Logging and Testing overlap. Once you have a set of classes in place — you build from that point. They should be worked/turned in as a single module each (one for Logging and one for Testing; under the same parent as Config).

A set of starter projects is available in assignment-starter/autorentals-starter. It is expected that you can implement the complete assignment on your own. However, the Maven poms and the portions unrelated to the assignment focus are commonly provided for reference to keep the focus on each assignment part. Your submission should not be a direct edit/hand-in of the starters. Your submission should — at a minimum:

  • use you own Maven groupIds

  • use your own Java package names below a given base

  • extend either spring-boot-starter-parent or ejava-build-parent

Your assignment submission should be a single-rooted source tree with sub-modules or sub-module trees for each independent area part. The assignment starters — again can be your guide for mapping these out.

Example Project Layout
|-- assignment1-autorentals-autoconfig
|   |-- pom.xml
|   |-- rentals-autoconfig-app
|   |-- rentals-autoconfig-toolrentals
|   `-- rentals-autoconfig-starter
|-- assignment1-autorentals-beanfactory  # 1st
|   |-- pom.xml
|   |-- rentals-beanfactory-app
|   |-- rentals-beanfactory-autorentals
|   `-- rentals-beanfactory-iface
|-- assignment1-autorentals-configprops
|   |-- pom.xml
|   `-- src
|-- assignment1-autorentals-logging
|   |-- pom.xml
|   `-- src
|-- assignment1-autorentals-propertysource
|   |-- pom.xml
|   `-- src
|-- assignment1-autorentals-testing
|   |-- pom.xml
|   `-- src
`-- pom.xml  <== your project root (separate from course examples tree)

1. Assignment 1a: App Config

1.1. @Bean Factory Configuration

1.1.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of configuring a decoupled application integrated using Spring Boot. You will:

  1. implement a service interface and implementation component

  2. package a service within a Maven module separate from the application module

  3. implement a Maven module dependency to make the component class available to the application module

  4. use a @Bean factory method of a @Configuration class to instantiate a Spring-managed component

1.1.2. Overview

In this portion of the assignment you will be implementing a component class and defining that as a Spring bean using a @Bean factory located within the core application JAR.

assignment1a race app beanfactory
Figure 1. @Bean Factory Configuration

1.1.3. Requirements

  1. Create an interface module with

    1. a RentalDTO class with name property. This is a simple data class.

    2. a RentalsService interface with a getRandomRental() method. This method returns a single RentalDTO instance.

  2. Create an AutoRental implementation module with

    1. AutoRental implementation implementing the RentalsService interface that returns a RentalDTO with a name (e.g., "autoRental0").

  3. Create an application module with

    1. a class that

      1. implements CommandLineRunner interface

      2. has the RentalsService component injected using constructor injection

      3. a run() method that

        1. calls the RentalsService for a random AutoRentalDTO

        2. prints a startup message with the DTO name

        3. relies on a @Bean factory to register it with the container and not a @Component mechanism

    2. a @Configuration class with two @Bean factory methods

      1. one @Bean factory method to instantiate a RentalsService autoRental implementation

      2. one @Bean factory method to instantiate the AppCommand injected with a RentalsService bean (not a POJO)

        @Bean factories that require external beans, can have the dependencies injected by declaring them in their method signature. Example:

        TypeA factoryA() {return new TypeA(); }
        TypeB factoryB(TypeA beanA) {return new TypeB(beanA); }

        That way the you can be assured that the dependency is a fully initialized bean versus a partially initialized POJO.

    3. a @SpringBootApplication class that initializes the Spring Context — which will process the @Configuration class

  4. Turn in a source tree with three or more complete Maven modules that will build and demonstrate a configured Spring Boot application.

1.1.4. Grading

Your solution will be evaluated on:

  1. implement a service interface and implementation component

    1. whether an interface module was created to contain interface and data dependencies of that interface

    2. whether an implementation module was created to contain a class implementation of the interface

  2. package a service within a Maven module separate from the application module

    1. whether an application module was created to house a @SpringBootApplication and @Configuration set of classes

  3. implement a Maven module dependency to make the component class available to the application module

    1. whether at least three separate Maven modules were created with a one-way dependency between them

  4. use a @Bean factory method of a @Configuration class to instantiate Spring-managed components

    1. whether the @Configuration class successfully instantiates the RentalsService component

    2. whether the @Configuration class successfully instantiates the AppCommand component injected with a RentalsService component.

1.1.5. Additional Details

  1. The spring-boot-maven-plugin can be used to both build the Spring Boot executable JAR and execute the JAR to demonstrate the instantiations, injections, and desired application output.

  2. A quick start project is available in assignment-starter/autorentals-starter/assignment1-autorentals-beanfactory. Modify Maven groupId and Java package if used.

1.2. Property Source Configuration

1.2.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of how to flexibly supply application properties based on application, location, and profile options. You will:

  1. implement value injection into a Spring Component

  2. define a default value for the injection

  3. specify property files from different locations

  4. specify a property file for a basename

  5. specify properties based on an active profile

  6. specify a both straight properties and YAML property file sources

1.2.2. Overview

You are given a Java application that prints out information based on injected properties, defaults, a base property file, and executed using different named profiles. You are to supply several profile-specific property files that — when processed together — produce the required output.

assignment1 autorentals app propsource
Figure 2. Property Source Configuration

This assignment involves no new Java coding (the "assignment starter" has all you need). It is designed as a puzzle where — given some constant surroundings — you need to determine what properties to supply and in which file to supply them, to satisfy all listed test scenarios.

The assignment is structured into two modules: app and support

  • app - is your assignment. The skeletal structure is provided in autorentals-starter/assignment1-autorentals-propertysource

  • support - is provided is provided in the autorentals-starter/autorentals-support-propertysource module and is to be used, unmodified through a Maven dependency. It contains a default application.properties file with skeletal values, a component that gets injected with property values, and a unit integration test that verifies the program results.

The autorentals-support-propertysource module provides the following resources.

PropertyCheck Class

This class has property injections defined with default values when they are not supplied. This class will be in your classpath and automatically packaged within your JAR.

Supplied Component Class
public class PropertyCheck implements CommandLineRunner {
    @Value("${spring.config.name:(default value)}") String configName;
    @Value("${spring.config.location:(default value)}") String configLocations;
    @Value("${spring.profiles.active:(default value)}") String profilesActive;

    @Value("${rentals.priority.source:not assigned}") String prioritySource;
    @Value("${rentals.db.url:not assigned}") String dbUrl;
application.properties File

This file provides a template of a database URL with placeholders that will get populated from other property sources. This file will be in your classpath and automatically packaged within your JAR.

Supplied application.properties File
#application.properties
rentals.priority.source=application.properties
rentals.db.user=user
rentals.db.port=00000
(1)
rentals.db.url=mongodb://${rentals.db.user}:${rentals.db.password}@${rentals.db.host}:${rentals.db.port}/test?authSource=admin
1 rentals.db.url is built from several property placeholders. password is not specified.
PropertySourceTest Class

a unit integration test is provided that can verify the results of your property file population. This test will run automatically during the Maven build.

Supplied Test Class
public class PropertySourceTest {
    static final String CONFIG_LOCATION="classpath:/,optional:file:src/locations/";
    class no_profile {
        @Test
        void has_expected_sources() throws Exception {
        @Test
        void has_generic_files_in_classpath() {
        @Test
        void has_no_credential_files_in_classpath() {
        @Test
        void sources_have_unique_properties() {
    class dev_dev1_profiles {
        @Test
        void has_expected_sources() throws Exception {
        @Test
        void sources_have_unique_properties() {
    class dev_dev2_profiles {
        @Test
        void has_expected_sources() throws Exception {
        @Test
        void sources_have_unique_properties() {
    class prd_site1_profiles {
        @Test
        void has_expected_sources() throws Exception {
        @Test
        void sources_have_unique_properties() {
    class prd_site2_profiles {
        @Test
        void has_expected_sources() throws Exception {
        @Test
        void sources_have_unique_properties() {
    class dev_dev1_dev2 {
        @Test
        void sources_have_unique_properties() {
    class prd_site1_site2 {
        @Test
        void sources_have_unique_properties() {

1.2.3. Requirements

The starter module has much of the setup already defined.
  1. Create a dependency on the support module. (provided in starter)

    Your Maven Dependency on Provided Assignment Artifacts
    <dependency>
        <groupId>info.ejava.assignments.propertysource.autorentals</groupId>
        <artifactId>autorentals-support-propertysource</artifactId>
        <version>${ejava.version}</version>
    </dependency>
  2. Add a @SpringBootApplication class with main() (provided in starter)

    Your @SpringBootApplication Class
    package info.ejava_student.starter.assignment1.propertysource.rentals;
    
    import info.ejava.assignments.propertysource.rentals.PropertyCheck;
    
    @SpringBootApplication
    public class PropertySourceApp {
  3. Provide the following property file sources. (provided in starter) application.properties will be provided through the dependency on the support module and will get included in the JAR.

    Your Property Classpath and File Path Files
    src/main/resources:/ (1)
        application-default.properties
        application-dev.yml (3)
        application-prd.properties
    src/locations/ (2)
        application-dev1.properties
        application-dev2.properties
        application-site1.properties
        application-site2.yml (3)
    1 src/main/resources files will get packaged into JAR and will automatically be in the classpath at runtime
    2 src/locations are not packaged into the JAR and will be referenced by a command-line parameter to add them to the config location path
    3 example uses of YAML files
    yml files must be expressed as a YAML file
    application-dev.yml and application-site2.yml must be expressed using YAML syntax
  4. Enable the unit integration test from the starter when you are ready to test — by removing @Disabled.

    package info.ejava_student.starter.assignment1.propertysource.rentals;
    
    import info.ejava.assignments.propertysource.rentals.PropertySourceTest;
    ...
    //we will cover testing in a future topic, very soon
    @Disabled //enable when ready to start assignment
    public class MyPropertySourceTest extends PropertySourceTest {
  5. Use a constant base command. This part of the command remains constant.

    $ mvn clean package -DskipTests=true (2)
    $ java -jar target/*-propertysource-1.0-SNAPSHOT-bootexec.jar --spring.config.location=classpath:/,optional:file:src/locations/ (1)
    1 this is the base command for 5 specific commands that specify profiles active
    2 need to skip tests to build JAR before complete

    The only modification to the command line will be the conditional addition of a profile activation.

    --spring.profiles.active= (1)
    1 the following 5 commands will supply a different value for this property
  6. Populate the property and YAML files so that the scenarios in the following paragraph are satisfied. The default starter with the "base command" and "no active profile" set, produces the following by default.

    $ java -jar target/*-propertysource-1.0-SNAPSHOT-bootexec.jar --spring.config.location=classpath:/,optional:file:src/locations/
    
    configName=(default value)
    configLocations=classpath:/,optional:file:src/locations/
    profilesActive=(default value)
    prioritySource=application-default.properties
    Rentals has started
    dbUrl=mongodb://user:NOT_SUPPLIED@NOT_SUPPLIED:00000/test?authSource=admin
    Any property value that does not contain a developer/site-specific value (e.g., defaultUser and defaultPass) must be provided by a property file packaged into the JAR (i.e., source src/main/resources)
    Any property value that does contain a developer/site-specific value (e.g., dev1pass and site1Pass) must be provided by a property file in the file: part of the location path and not in the JAR (i.e., source src/locations).

    Complete the following 5 scenarios:

    1. No Active Profile Command Result

      configName=(default value)
      configLocations=classpath:/,optional:file:src/locations/
      profilesActive=(default value)
      prioritySource=application-default.properties
      Rentals has started
      dbUrl=mongodb://defaultUser:defaultPass@defaulthost:27027/test?authSource=admin
      You must supply a populated set of configuration files so that, under this option, user:NOT_SUPPLIED@NOT_SUPPLIED:00000 becomes defaultUser:defaultPass@defaulthost:27027.
    2. dev,dev1 Active Profile Command Result

      --spring.profiles.active=dev,dev1
      configName=(default value)
      configLocations=classpath:/,optional:file:src/locations/
      profilesActive=dev,dev1
      prioritySource=application-dev1.properties
      Rentals has started
      dbUrl=mongodb://devUser:dev1pass@127.0.0.1:11027/test?authSource=admin
    3. dev,dev2 Active Profile Command Result

      --spring.profiles.active=dev,dev2
      configName=(default value)
      configLocations=classpath:/,optional:file:src/locations/
      profilesActive=dev,dev2
      prioritySource=application-dev2.properties
      Rentals has started
      dbUrl=mongodb://devUser:dev2pass@127.0.0.1:22027/test?authSource=admin
      The development profiles share the same user and host.
    4. prd,site1 Active Profile Command Result

      --spring.profiles.active=prd,site1
      configName=(default value)
      configLocations=classpath:/,optional:file:src/locations/
      profilesActive=prd,site1
      prioritySource=application-site1.properties
      Rentals has started
      dbUrl=mongodb://prdUser:site1pass@db.site1.net:27017/test?authSource=admin
    5. prd,site2 Active Profile Command Result

      --spring.profiles.active=prd,site2
      configName=(default value)
      configLocations=classpath:/,optional:file:src/locations/
      profilesActive=prd,site2
      prioritySource=application-site2.properties
      Rentals has started
      dbUrl=mongodb://prdUser:site2pass@db.site2.net:27017/test?authSource=admin
      The production/site profiles share the same user and port.
  7. No property with the same value may be present in multiple property files. You must make use of property source inheritance when requiring a common value.

  8. Turn in a source tree with a complete Maven module that will build and demonstrate the @Value injections for the different active profile settings.

1.2.4. Grading

Your solution will be evaluated on:

  1. implement value injection into a Spring Component

    1. whether @Component attributes were injected with values from property sources

  2. define a default value for the injection

    1. whether default values were correctly accepted or overridden

    2. whether each unique property value was expressed in a single source file and property file inheritance was used when common values were needed

  3. specify property files from different locations

    1. whether your solution provides property values coming from multiple file locations

      1. any property value that does not contain a developer/site-specific value (e.g., defaultUser and defaultPass) must be provided by a property file within the JAR

      2. any property value that contains developer/site-specific values (e.g., dev1pass and site1pass) must be provided by a property file outside of the JAR

    2. the given application.properties file may not be modified

    3. named .properties files are supplied as properties files

    4. named .yml (i.e., application-dev.yml) files are supplied as YAML files

  4. specify properties based on an active profile

    1. whether your output reflects current values for dev1,dev2,site1, and site2 profiles

  5. specify both straight properties and YAML property file sources

    1. whether your solution correctly supplies values for at least 1 properties file

    2. whether your solution correctly supplies values for at least 1 YAML file

1.2.5. Additional Details

  1. The spring-boot-maven-plugin can be used to both build the Spring Boot executable JAR and demonstrate the instantiations, injections, and desired application output.

  2. A quick start project is available in assignment-starter/autorentals-starter/assignment1-autorentals-propertysource that supplies much of the boilerplate file and Maven setup. Modify Maven groupId and Java package if used.

  3. An integration unit test (PropertySourceTest) is provided within the support module that can automate the verifications.

  4. Ungraded Question to Ponder: How could you at runtime, provide a parameter option to the application to make the following output appear?

    Alternate Output
    configName=autorentals
    configLocations=(default value)
    profilesActive=(default value)
    prioritySource=not assigned
    Rentals has started
    dbUrl=not assigned

1.3. Configuration Properties

1.3.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of injecting properties into a @ConfigurationProperties class to be injected into components - to encapsulate the runtime configuration of the component(s). You will:

  1. map a Java @ConfigurationProperties class to a group of properties

  2. create a read-only @ConfigurationProperties class using @ConstructorBinding

  3. define a Jakarta EE Java validation rule for a property and have the property validated at runtime

  4. generate boilerplate JavaBean methods using Lombok library

  5. map nested properties to a @ConfigurationProperties class

  6. reuse a @ConfigurationProperties class to map multiple property trees of the same structure

  7. use @Qualifier annotation and other techniques to map or disambiguate an injection

1.3.2. Overview

In this assignment, you are going to finish mapping a YAML file of properties to a set of Java classes and have them injected as @ConfigurationProperty beans.

assignment1a race app configprops

BoatRentalProperties is a straight-forward, single use bean that can have the class directly mapped to a specific property prefix. RentalsProperties will be mapped to two separate prefixes — so the mapping cannot be applied directly to that class. Keep this in mind when wiring up your solution.

An integration unit test is supplied and can be activated when you are ready to test your progress.

1.3.3. Requirements

  1. Given the following read-only property classes, application.yml file, and @Component …​

    1. read-only property classes

      @Value
      public class RentalProperties {
          private int id;
          private LocalDate rentalDate;
          private BigDecimal rentalAmount;
          private String renterName;
          private AddressProperties location;
      }
      @Value
      public class BoatRentalProperties {
          private int id;
          private LocalDate rentalDate;
          private BigDecimal rentalAmount;
          private String renterName;
          private AddressProperties location;
      }
      @Value
      public class AddressProperties {
          private final String city;
          private final String state;
      }
      Lombok @Value annotation defines the class to be read-only by only declaring getter()s and no setter()s. This will require use of constructor binding.

      The property classes are supplied in the starter module.

    2. application.yml YAML file

      rentals:
        autos:
          - id: 1
            rentalDate: 2010-07-01 (6)
            rentalAmount: 100.00 (1)
            renterName: Joe Camper
            location:
              city: Jonestown
              state: PA
      #...
        tools:
          - id: 2
            rental-date: 2000-01-01
            rental-amount: 1000 (2)
            renter_name: Itis Clunker (3)
            location:
              city: Dundalk
              state: MD
      boatRental:
        id: 3
        RENTAL_DATE: 2022-08-01 (4)
        RENTAL_AMOUNT: 200_000
        RENTER-NAME: Alexus Blabidy (5)
        LOCATION:
          city: Annapolis
          state: MD
      1 lower camelCase
      2 lower kabob-case
      3 lower snake_case
      4 upper SNAKE-CASE
      5 upper KABOB-CASE
      6 LocalDate parsing will need to be addressed

      The full contents of the YAML file can be found in the autorentals-support/autorentals-support-configprops support project. YAML was used here because it is easier to express and read the nested properties.

      Notice that multiple text cases (upper, lower, snake, kabob) are used to map the the same Java properties. This demonstrates one of the benefits in using @ConfigurationProperties over @Value injection — configuration files can be expressed in syntax that may be closer to the external domain.

      Note that the LocalDate will require additional work to parse. That is provided to you in the starter project and described later in this assignment.

    3. @Component with constructor injection and getters to inspect what was injected

      //@Component
      @Getter
      @RequiredArgsConstructor
      public class PropertyPrinter implements CommandLineRunner {
          private final List<RentalProperties> autos;
          private final List<RentalProperties> tools;
          private final BoatRentalProperties boat;
      
          @Override
          public void run(String... args) throws Exception {
              System.out.println("autos:" + format(autos));
              System.out.println("tools:" + format(tools));
              System.out.println("boat:" + format(null==boat ? null : List.of(boat)));
          }
      
          private String format(List<?> rentals) {
              return null==rentals ? "(null)" :
                  String.format("%s", rentals.stream()
                      .map(r->"*" + r.toString())
                      .collect(Collectors.joining(System.lineSeparator(), System.lineSeparator(), "")));
          }
      }

      The source for the PropertyPrinter component is supplied in the starter module. Except for getting it registered as a component, there should be nothing needing change here.

  2. When running the application, a @ConfigurationProperties beans will be created to represent the contents of the YAML file as two separate List<RentalProperties> objects and a BoatRentalProperties object. When properly configured, they will be injected into the @Component, and and it will output the following.

    autos:
    *RentalsProperties(id=1, rentalDate=2010-07-01, rentalAmount=100.0, renterName=Joe Camper, location=AddressProperties(city=Jonestown, state=PA))
    *RentalsProperties(id=4, rentalDate=2021-05-01, rentalAmount=500000, renterName=Jill Suburb, location=AddressProperties(city=, state=MD)) (1)
    *RentalPropertiesProperties(id=5, rentalDate=2021-07-01, rentalAmount=1000000, renterName=M.R. Bigshot, location=AddressProperties(city=Rockville, state=MD))
    autos:
    *RentalProperties(id=2, rentalDate=2000-01-01, rentalAmount=1000, renterName=Itis Clunker, location=AddressProperties(city=Dundalk, state=MD))
    boat:
    *BoatRentalProperties(id=3, rentalDate=2022-08-01, rentalAmount=200000, renterName=Alexus Blabidy, location=AddressProperties(city=Annapolis, state=MD))
    1 one of the autoRentals addresses is missing a city
    The "assignment starter" supplies most of the Java code needed for the PropertyPrinter.
  3. Configure your solution so that the BoatRentalProperties bean is injected into the PropertyPrinter component along with the List of auto and tool RentalProperties. There is a skeletal configuration supplied in the application class. Most of your work will be within this class.

    @SpringBootApplication
    public class ConfigPropertiesApp {
        public static void main(String[] args)
    
        public List<RentalsProperties> autos() {
            return new ArrayList<>();
        }
        public List<RentalsProperties> tools() {
            return new ArrayList<>();
        }
    }
  4. Turn in a source tree with a complete Maven module that will build and demonstrate the configuration property processing and output of this application.

1.3.4. Grading

Your solution will be evaluated on:

  1. map a Java @ConfigurationProperties class to a group of properties

    1. whether Java classes were used to map values from the given YAML file

  2. create a read-only @ConfigurationProperties class using @ConstructorBinding

    1. whether read-only Java classes, using @ConstructorBinding were used to map values from the given YAML file

  3. generate boilerplate JavaBean methods using Lombok library

    1. whether lombok annotations were used to generate boilerplate Java bean code

  4. map nested properties to a @ConfigurationProperties class

    1. whether nested Java classes were used to map nested properties from a given YAML file

  5. reuse a @ConfigurationProperties class to map multiple property trees of the same structure

    1. whether multiple property trees were instantiated using the same Java classes

  6. use @Qualifier annotation and other techniques to map or disambiguate an injection

    1. whether multiple @ConfigurationProperty beans of the same type could be injected into a @Component using a disambiguating technique.

1.3.5. Additional Details

  1. A starter project is available in autorentals-starter/assignment1-autorentals-configprops. Modify Maven groupId and Java package if used.

  1. The spring-boot-maven-plugin can be used to both build the Spring Boot executable JAR and demonstrate the instantiations, injections, and desired application output.

  2. The support project contains an integration unit test that verifies the PropertyPrinter component was defined and injected with the expected data. It is activated through a Java class in the starter module. Activate it when you are ready to test.

    //we will cover testing in a future topic, very soon
    @Disabled //remove to activate when ready to test
    public class MyConfigurationTest extends ConfigurationPropertyTest {
    }
  3. Ungraded Question to Ponder: What change(s) could be made to the application to validate the properties and report the following error?

    Alternate Output
    Binding validation errors on rentals.autos[1].location
    ...
    codes [rentals.autos[1].location.city,city]; arguments []; default message [city]]; default message [must not be blank]

1.4. Auto-Configuration

1.4.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of developing @Configuration classes used for Auto-Configuration of an application.

You will:

  1. Create a @Configuration class or @Bean factory method to be registered based on the result of a condition at startup

  2. Create a Spring Boot Auto-configuration module to use as a "Starter"

  3. Bootstrap Auto-configuration classes into applications using an …​ AutoConfiguration.imports metadata file

  4. Create a conditional component based on the presence of a property value

  5. Create a conditional component based on a missing component

  6. Create a conditional component based on the presence of a class

  7. Define a processing dependency order for Auto-configuration classes

1.4.2. Overview

In this assignment, you will be building a starter module, with a prioritized list of Auto-Configuration classes that will bootstrap an application depending on runtime environment. This application will have one (1) type of RentalsService out of a choice of two (2) based on the environment at runtime.

assignment1a race app autoconfig1
Make the @SpringBootApplication class package independent of @Configuration class packages
The Java package for the @SpringBootApplication class must not be a parent or at the same Java package as the @Configuration classes. Doing so, would place the @Configuration classes in the default component scan path and make them part of the core application — versus a conditional extension of the application.

1.4.3. Requirements

You have already implemented the RentalsService interface and AutoRentals implementation modules in your Bean Factory solution. You will reuse them through a Maven dependency. ToolRentals implementation is a copy of AutoRentals implementation with name changes.
  1. Create a RentalsService interface module (already completed for beanfactory)

    1. Add an interface to return a random rental as a RentalDTO instance

  2. Create a AutoRentals implementation implementation module (already completed for beanfactory)

    1. Add an implementation of the interface to return a RentalDTO with "auto" in the name property.

  3. Create a ToolRentals implementation implementation module (new)

    1. Add an implementation of the interface to return a RentalDTO with "tool" in the name property.

  4. Create an Application Module with a @SpringBootApplication class

    1. Add a CommandLineRunner implementation class that gets injected with a RentalsService bean and prints "Rentals has started" with the name of rental coming from the injected bean.

      1. Account for a null implementation injected when there is no implementation such that it still quietly prints the state of the component.

      2. Include an injection (by any means) for properties rentals.active and rentals.preference to print their values. Account for when they are not supplied.

    2. Add a @Bean factory for the CommandLineRunner implementation class — registered as "appCommand".

      1. Make the injection of the RentalsService optional to account for when there is no implementation

        @Autowired(required=false)
    3. Do not place any direct Java or Maven dependencies from the Application Module to the RentalService implementation modules.

      At this point you are have mostly repeated the bean factory solution except that you have eliminated the @Bean factory for the RentalsService in the Application module, added a ToolRental implementation option, and removed a few Maven module dependencies.
  5. Create a Rental starter Module

    1. Add a dependency on the RentalsService interface module

    2. Add a dependency on the RentalsService implementation modules and make them "optional" (this is important) so that the application module will need to make an explicit dependency on the implementation for them to be on the runtime classpath.

    3. Add three conditional Auto-configuration classes

      1. one that provides a @Bean factory for the ToolRentalsService implementation class

        1. Make this conditional on the presence of the ToolRental class(es) being available on the classpath

      2. one that provides a @Bean factory for the AutoRentalsService implementation class

        1. Make this conditional on the presence of the AutoRental class(es) being available on the classpath

      3. A third that provides another @Bean factory for the ToolRental implementation class

        1. Make this conditional on the presence of the ToolRental class(es) being available on the classpath

        2. Make this also conditional on the property rentals.preference having the value of tools.

    4. Set the following priorities for the Auto-configuration classes

      1. make the ToolRental/property (3rd from above) the highest priority

      2. make the AutoRental factory the next highest priority

      3. make the ToolRental factory the lowest priority

        You can use org.springframework.boot.autoconfigure.AutoConfigureOrder to set a relative order — with the lower value having a higher priority.
    5. Disable all RentalsService implementation @Bean factories if the property rentals.active is present and has the value false

      Treat false as being not the value true. Spring Boot does not offer a disable condition, so you will be looking to enable when the property is true or missing.
    6. Perform necessary registration steps within the Starter module to make the Auto-configuration classes visible to the application bootstrapping.

      If you don’t know how to register an Auto-configuration class and bypass this step, your solution will not work.
      Spring Boot only prioritizes explicitly registered Auto-configuration classes and not nested classes @Configuration classes within them.
  6. Augment the Application module pom to address dependencies

    1. Add a dependency on the Starter Module

    2. Create a profile (autos) that adds a direct dependency on the AutoRentals implementation module. The "assignment starter" provides an example of this.

    3. Create a profile (tools) that adds a direct dependency on the ToolRentals implementation module.

  7. Verify your solution will determine its results based on the available classes and properties at runtime. Your solution must have the following behavior

    1. no Maven profiles active and no properties provided

      $ mvn dependency:list -f *-autoconfig-app | egrep 'ejava-student.*module'
      (starter module)
      (interface module)
      (1)
      
      $ mvn clean package
      $ java -jar *-autoconfig-app/target/*-autoconfig-app-*-bootexec.jar
      
      rentals.active=(not supplied)
      rentals.preference=(not supplied)
      Rentals is not active (2)
      1 no RentalsService implementation jars in dependency classpath
      2 no implementation was injected because none in the classpath
    2. autos only Maven profile active and no properties provided

      $ mvn dependency:list -f *-autoconfig-app -P autos | egrep 'ejava-student.*module'
      (starter module)
      (interface module)
      (AutoRentals implementation module) (1)
      
      $ mvn clean package -P autos
      $ java -jar *-autoconfig-app/target/*-autoconfig-app-*-bootexec.jar
      
      rentals.active=(not supplied)
      rentals.preference=(not supplied)
      Rentals has started, rental:{autoRental0} (2)
      1 AutoRentals implementation JAR in dependency classpath
      2 AutoRentalsService was injected because only implementation in classpath
    3. tools only Maven profile active and no properties provided

      $ mvn dependency:list -f *-autoconfig-app -P tools | egrep 'ejava-student.*module'
      (starter module)
      (interface module)
      (ToolRentals implementation module)  (1)
      
      $ mvn clean package -P tools
      $ java -jar *-autoconfig-app/target/*-autoconfig-app-*-bootexec.jar
      
      rentals.active=(not supplied)
      rentals.preference=(not supplied)
      Rentals has started, rental:{toolRental0} (2)
      1 ToolRentals implementation JAR in dependency classpath
      2 ToolRentalsService was injected because only implementation in classpath
    4. autos and tools Maven profiles active

      $ mvn dependency:list -f *-autoconfig-app -P tools,autos | egrep 'ejava-student.*module'
      (starter module)
      (interface module)
      (AutoRentals implementation module) (1)
      (ToolRentals implementation module) (2)
      
      $ mvn clean install -P autos,autos
      $ java -jar *-autoconfig-app/target/*-autoconfig-app-*-bootexec.jar
      
      rentals.active=(not supplied)
      rentals.preference=(not supplied)
      Rentals has started, rental:{autoRental0} (3)
      1 AutoRentals implementation JAR in dependency classpath
      2 ToolRentals implementation JAR in dependency classpath
      3 AutoRentalsService was injected because of higher-priority
    5. autos and tools Maven profiles active and Spring property Rental.preference=tools

      $ mvn clean install -P autos,autos (1)
      java -jar rentals-autoconfig-app/target/rentals-autoconfig-app-1.0-SNAPSHOT-bootexec.jar --rentals.preference=autos (2)
      
      rentals.active=(not supplied)
      rentals.preference=autos
      Rentals has started, rental:{toolRental0} (3)
      1 AutoRental and ToolRental implementation JARs in dependency classpath
      2 rentals.preference property supplied with autos value
      3 ToolRentalsService implementation was injected because of preference specified
    6. autos and tools Maven profiles active and Spring property rentals.active=false

      $ mvn clean install -P autos,autos (1)
      
      $ java -jar rentals-autoconfig-app/target/rentals-autoconfig-app-1.0-SNAPSHOT-bootexec.jar --rentals.active=false (2)
      
      rentals.active=false
      rentals.preference=(not supplied)
      Rentals is not active (3)
      1 AutoRental and ToolRental implementation JARs in dependency classpath
      2 rentals.active property supplied with false value
      3 no implementation was injected because feature deactivated with property value
  8. Turn in a source tree with a complete Maven module that will build and demonstrate the Auto-Configuration property processing and output of this application.

1.4.4. Grading

Your solution will be evaluated on:

  1. Create a @Configuration class/@Bean factory method to be registered based on the result of a condition at startup

    1. whether your solution provides the intended implementation class based on the runtime environment

  2. Create a Spring Boot Auto-configuration module to use as a "Starter"

    1. whether you have successfully packaged your @Configuration classes as Auto-Configuration classes outside the package scanning of the @SpringBootApplication

  3. Bootstrap Auto-configuration classes into applications using a AutoConfiguration.imports metadata file

    1. whether you have bootstrapped your Auto-Configuration classes so they are processed by Spring Boot at application startup

  4. Create a conditional component based on the presence of a property value

    1. whether you activate or deactivate a @Bean factory based on the presence or absence of a specific the a specific property

  5. Create a conditional component based on a missing component

    1. whether you activate or deactivate a @Bean factory based on the presence or absence of a specific @Component

  6. Create a conditional component based on the presence of a class

    1. whether you activate or deactivate a @Bean factory based on the presence or absence of a class

    2. whether your starter causes unnecessary dependencies on the Application module

  7. Define a processing dependency order for Auto-configuration classes

    1. whether your solution is capable of implementing the stated priorities of which bean implementation to instantiate under which conditions

1.4.5. Additional Details

  1. A starter project is available in autorentals-starter/assignment1-autorentals-autoconfig. Modify Maven groupId and Java package if used.

  2. A unit integration test is supplied to check the results. We will cover testing very soon. Activate the test when you are ready to get feedback results. The test requires:

    • All classes be below the info.ejava_student Java package

    • The component class injected with the dependency have the bean identity of appCommand.

    • The injected service made available via getRentalsService() method within appCommand.

2. Assignment 1b: Logging

2.1. Application Logging

2.1.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of injecting and calling a logging framework. You will:

  1. obtain access to an SLF4J Logger

  2. issue log events at different severity levels

  3. format log events for regular parameters

  4. filter log events based on source and severity thresholds

2.1.2. Overview

In this portion of the assignment, you are going to implement a call thread through a set of components that are in different Java packages that represent at different levels of the architecture. Each of these components will setup an SLF4J Logger and issue logging statements relative to the thread.

assignment1b autorentals app logger
Figure 3. Application Logging

2.1.3. Requirements

All data is fake and random here. The real emphasis should be placed on the logging events that occur on the different loggers and not on creating a realistic AutoRental result.
  1. Create several components in different Java sub-packages (app, svc, and repo)

    1. an AppCommand component class in the app Java sub-package

    2. a AutoRentalsServiceImpl component class in the svc Java sub-package

    3. a AutoRentalsHelperImpl component class in the svc Java sub-package

    4. a AutoRentalsRepositoryImpl component class in the repo Java sub-package

  2. Implement a chain of calls from the AppCommand @Component run() method through the other components.

    assignment1b autorentals app logger sequence
    Figure 4. Required Call Sequence
    1. AppCommand.run() logs message with text "AutoRentals has started"

    2. AppCommand.run() calls ServiceImpl.calcDelta(autoId, renterId) with a autoId and renterId to determine a delta between the two instances

    3. ServiceImpl.calcDelta(autoId, renterId) calls RepositoryImpl (getLeaderByAutoId(autoId) and getByRenterId(renterId)) to get AutoRentalDTOs

      1. RepositoryImpl can create transient instances with provided Ids and fake remaining properties

    4. ServiceImpl.calcDelta(autoId, renterId) also calls ResultsHelper.calcDelta() to get a delta between the two AutoRentalDTOs

    5. HelperImpl.calcDelta(leader, target) calls AutoRentalDTO.getAmount() on the two provided AutoRentalDTO instances to determine the delta

      The focus of logging and this assignment really starts at this point and forward. I have considered writing the interaction logic above for you to eliminate this distraction within a logging assignment. However, I found that at this early point in the semester and assignments — this is also a good time to practice essential skills of creating components with dependencies and simple interactions.
  3. Implement a toString() method in AutoRentalDTO that includes the autoId, renterId, and amount information.

  4. Instantiate an SLF4J Logger into each of the four components

    1. manually instantiate a static final Logger with the name "X.Y" in AppCommand

    2. leverage the Lombok library to instantiate a Logger with a name based on the Java package and name of the hosting class for all other components

  5. Implement logging statements in each of the methods

    1. the severity of RepositoryImpl logging events are all TRACE

    2. the severity of HelperImpl.calcDelta() logging events are DEBUG and TRACE (there must be at least two — one of each and no other levels)

    3. the severity of ServiceImpl.calcDelta() logging events are all INFO and TRACE (there must be at least two — one of each and no other levels)

    4. the severity of AppCommand logging events are all INFO (and no other levels)

  6. Output available results information in log statements

    1. Leverage the SLF4J parameter formatting syntax when implementing the log

    2. For each of the INFO and DEBUG statements, include only the AutoRentalDTO property values (e.g., autoId, renterId, timeDelta)

      Use direct calls on individual properties for INFO and DEBUG statements
      i.e., autoRental.getAutoId(), autoRental.getRenterId(), etc.
    3. For each of the TRACE statements, use the inferred AutoRentalDTO.toString() method to log the AutoRentalDTO.

      Use inferred toString() on passed object on TRACE statements
      i.e., log.debug("…​", autoRental) — no direct calls to toString()
  7. Supply two profiles

    1. the root logger must be turned off by default (e.g., in application.properties)

    2. an app-debug profile that turns on DEBUG and above priority (e.g., DEBUG, INFO, WARN, ERROR) logging events for all loggers in the application, including "X.Y"

    3. a repo-only profile that turns on only log statements from the repo class(es).

  8. Wrap your solution in a Maven build that executes the JAR three times with:

    1. (no profile) - no logs should be produced

    2. app-debug profile

      1. DEBUG and higher priority logging events from the application (including "X.Y") are output to console

      2. no TRACE messages are output to the console

    3. repo-only profile

      1. logging events from repository class(es) are output to the console

      2. no other logging events are output to the console

2.1.4. Grading

Your solution will be evaluated on:

  1. obtain access to an SLF4J Logger

    1. whether you manually instantiated a Logger into the AppCommand @Component

    2. whether you leveraged Lombok to instantiate a Logger into the other @Components

    3. whether your App Command @Component logger was named "X.Y"

    4. whether your other @Component loggers were named after the package/class they were declared in

  2. issue log events at different severity levels

    1. where logging statements issued at the specified verbosity levels

  3. format log events for regular parameters

    1. whether SLF4J format statements were used when including variable information

  4. filter log events based on source and severity thresholds

    1. whether your profiles set the logging levels appropriately to only output the requested logging events

2.1.5. Other Details

  1. You may use any means to instantiate/inject the components (i.e., @Bean factories or @Component annotations)

  2. You are encouraged to use Lombok to declare constructors, getter/setter methods, and anything else helpful except for the manual instantiation of the "X.Y" logger in AppCommand.

  3. A starter project is available in autorentals-starter/assignment1-autorentals-logging. It contains a Maven pom that is configured to build and run the application with the following profiles for this assignment:

    • no profile

    • app-debug

    • repo-only

    • appenders (used later in this assignment)

    • appenders and trace

    Modify Maven groupId and Java package if used.

  4. There is an integration unit test (MyLoggingNTest) provided in the starter module. We will discuss testing very soon. Enable this test when you are ready to have the results evaluated.

2.2. Logging Efficiency

2.2.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of making suppressed logging efficient. You will:

  1. efficiently bypass log statements that do not meet criteria

2.2.2. Overview

In this portion of the assignment, you are going to increase the cost of calling toString() on the logged object and work to only pay that penalty when needed.

Make your changes to the previous logging assignment solution. Do not create a separate module for this work.
assignment1b autorentals log efficiency png

2.2.3. Requirements

  1. Update the toString() method in AutoRentalDTO to be expensive to call

    1. artificially insert a 750 milliseconds delay within the toString() call

  2. Refactor your log statements, if required, to only call toString() when TRACE is active

    1. leverage the SLF4J API calls to make that as simple as possible

2.2.4. Grading

Your solution will be evaluated on:

  1. efficiently bypass log statements that do not meet criteria

    1. whether your toString() method paused the calling thread for 750 milliseconds when TRACE threshold is activated

    2. whether the calls to toString() are bypassed when priority threshold is set higher than TRACE

    3. the simplicity of your solution

2.2.5. Other Details

  1. Include these modifications with the previous work on this overall logging assignment. Meaning — there will not be a separate module turned in for this portion of the assignment.

  2. The app-debug should not exhibit any additional delays. The repo-only should exhibit a 1.5 (2x750msec) second delay.

  3. There is an integration unit test (MyLoggingEfficiencyNTest) provided in the starter module. We will discuss testing very soon. Enable this test when you are ready to have the results evaluated.

2.3. Appenders and Custom Log Patterns

2.3.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of assigning appenders to loggers and customizing logged events. You will:

  1. filter log events based on source and severity thresholds

  2. customize log patterns

  3. customize appenders

  4. add contextual information to log events using Mapped Diagnostic Context

  5. use Spring Profiles to conditionally configure logging

2.3.2. Overview

In this portion of the assignment you will be creating/configuring a few basic appenders and mapping loggers to them — to control what, where, and how information is logged. This will involve profiles, property files, and a logback configuration file.

Make your changes to the original logging assignment solution. Do not create a separate module for this work.
assignment1b autorentals log appenders png
Figure 5. Appenders and Custom Log Patterns
Except for setting the MDC, you are writing no additional code in this portion of the assignment. Most of your work will be in filling out the logback configuration file and setting properties in profile-based property files to tune logged output.

2.3.3. Requirements

  1. Declare two Appenders as part of a custom Logback configuration

    1. CONSOLE appender to output to stdout

    2. FILE appender to output to a file target/logs/appenders.log

  2. Assign the Appenders to Loggers

    1. root logger events must be assigned to the CONSOLE Appender

    2. any log events issued to the "X.Y" Logger must be assigned to both the CONSOLE and FILE Appenders

    3. any log events issued to the "…​svc" Logger must also be assigned to both the CONSOLE and FILE Appenders

    4. any log events issued to the "…​repo" Logger must only be assigned to the FILE Appender

      Remember "additivity" rules for inheritance and appending assignment
      These are the only settings you need to make within the Appender file. All other changes can be done through properties. However, there will be no penalty (just complexity) in implementing other mechanisms.
  3. Add an appenders profile that

    1. automatically enacts the requirements above

    2. sets a base of INFO severity and up for all loggers with your application

  4. Add a requestId property to the Mapped Diagnostic Context (MDC)

    assignment1b autorentals app logger mdc
    Figure 6. Initialize Mapped Diagnostic Context (MDC)
    1. generate a random/changing value using a 36 character UUID String

      Example: UUID.randomUUID().toString()d587d04c-9047-4aa2-bfb3-82b25524ce12
    2. insert the value prior to the first logging statement — in the AppCommand component

    3. remove the MDC property when processing is complete within the AppCommand component

  5. Declare a custom logging pattern in the appenders profile that includes the MDC requestId value in each log statements written by the FILE Appender

    1. The MDC requestId is only output by the FILE Appender. Encase the UUID within square [] brackets so that it can be found in a pattern search more easily

      Example: [d587d04c-9047-4aa2-bfb3-82b25524ce12]
    2. The MDC requestId is not output by the CONSOLE Appender

  6. Add an additional trace profile that

    1. activates logging events at TRACE severity and up for all loggers with your application

    2. adds method and line number information to all entries in the FILE Appender but not the CONSOLE Appender. Use a format of method:lineNumber in the output.

      Example: run:27
      Optional: Try defining the logging pattern once with an optional property variable that can be used to add method and line number expression versus repeating the definition twice.
  7. Apply the appenders profile to

    1. output logging events at INFO severity and up to both CONSOLE and FILE Appenders

    2. include the MDC requestId in events logged by the FILE Appender

    3. not include method and line number information in events logged

  8. Apply the appenders and trace profiles to

    1. output logging events at TRACE severity and up to both CONSOLE and FILE Appenders

    2. continue to include the MDC requestId in events logged by the FILE Appender

    3. add method and line number information in events logged by the FILE Appender

2.3.4. Grading

Your solution will be evaluated on:

  1. filter log events based on source and severity thresholds

    1. whether your log events from the different Loggers were written to the required appenders

    2. whether a log event instance appeared at most once per appender

  2. customize log patterns

    1. whether your FILE Appender output was augmented with the requestId when appenders profile was active

    2. whether your FILE Appender output was augmented with method and line number information when trace profile was active

  3. customize appenders

    1. whether a FILE and CONSOLE appender were defined

    2. whether a custom logging pattern was successfully defined for the FILE Logger

  4. add contextual information to log events using Mapped Diagnostic Context

    1. whether a requestId was added to the Mapped Data Context (MDC)

    2. whether the requestId was included in the customized logging pattern for the FILE Appender when the appenders profile was active

  5. use Spring Profiles to conditionally configure logging

    1. whether your required logging configurations where put in place when activating the appenders profile

    2. whether your required logging configurations where put in place when activating the appenders and trace profiles

2.3.5. Other Details

  1. You may use the default Spring Boot LogBack definition for the FILE and CONSOLE Appenders (i.e., include them in your logback configuration definition).

    Included Default Spring Boot LogBack definitions
    <configuration>
        <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
        <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
        <include resource="org/springframework/boot/logging/logback/file-appender.xml"/>
    ...
  2. Your appenders and trace profiles may re-define the logging pattern for the FILE logger or add/adjust parameterized definitions. However, try to implement an optional parameterization as your first choice to keep from repeating the same definition.

  3. The following snippet shows an example resulting logfile from when appenders and then appenders,trace profiles were activated. Yours may look similar to the following:

    Example target/logs/appenders.log - "appenders" profile active
    $ rm target/logs/appenders.log
    $ java -jar target/assignment1-*-logging-1.0-SNAPSHOT-bootexec.jar --spring.profiles.active=appenders
    $ head target/logs/appenders.log
    
    head target/logs/appenders.log       (1)
    21:46:01.335  INFO -- [c934e045-1294-43c9-8d22-891eec2b8b84]  Y : AutoRentals has started (2)
    1 requestId is supplied in all FILE output when appenders profile active
    2 no method and line number info supplied
    Example target/logs/appenders.log - "appenders,trace" profiles active
    $ rm target/logs/appenders.log
    $ java -jar target/assignment1-*-logging-1.0-SNAPSHOT-bootexec.jar --spring.profiles.active=appenders,trace
    $ head target/logs/appenders.log
    
    $ head target/logs/appenders.log             (1)
    21:47:33.784  INFO -- [0289d00e-5b28-4b01-b1d5-1ef8cf203d5d]  Y.run:27 : AutoRentals has started (2)
    1 requestId is supplied in all FILE output when appenders profile active
    2 method and line number info are supplied
  4. There is a set of unit integration tests provided in the support module. We will cover testing very soon. Enable them when you are ready to evaluate your results.

3. Assignment 1c: Testing

The following parts are broken into different styles of conducting a pure unit test and unit integration test — based on the complexity of the class under test. None of the approaches are deemed to be "the best" for all cases.

  • tests that run without a Spring context can run blazingly fast, but lack the target runtime container environment

  • tests that use Mocks keep the focus on the subject under test, but don’t verify end-to-end integration

  • tests that assemble real components provide verification of end-to-end capability but can introduce additional complexities and performance costs

It is important that you come away knowing how to implement the different styles of unit testing so that they can be leveraged based on specific needs.

3.1. Demo

The assignment1-autorentals-testing assignment starter contains a @SpringBootApplication main class and a some demonstration code that will execute at startup when using the demo profile.

Application Demonstration
$ mvn package -Pdemo
06:34:21.217  INFO -- RentersServiceImpl : renter added: RenterDTO(id=null, firstName=warren, lastName=buffet, dob=1930-08-30)
06:34:21.221  INFO -- RentersServiceImpl : invalid renter: RenterDTO(id=null, firstName=future, lastName=buffet, dob=2023-07-14), [renter.dob: must be greater than 12 years]

You can follow that thread of execution through the source code to get better familiarity with the code you will be testing.

Starter Contains 2 Example Ways to Implement CommandLineRunner

There are actually 2 sets of identical output generated during the provided demo execution. The starter is supplied with two example ways to implement a CommandLineRunner: as a class and as a lambda function.

Implementing CommandLineRunner as a Class that Implements Interface
@Component
@RequiredArgsConstructor
static class Init implements CommandLineRunner {
    private final RentersService rentersService;
    @Override
    public void run(String... args) {
Returning a Lambda Function that Implements Interface
@Bean
CommandLineRunner lambdaDemo(RentersService rentersService) {
    return (args)->{

3.2. Unit Testing

3.2.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of implementing a unit test for a Java class. You will:

  1. write a test case and assertions using JUnit 5 "Jupiter" constructs

  2. leverage the AssertJ assertion library

  3. execute tests using Maven Surefire plugin

3.2.2. Overview

In this portion of the assignment, you are going to implement a test case with 2 unit tests for a completed Java class.

assignment1c autorentals app testing
Figure 7. Unit Test

The code under test is 100% complete and provided to you in a separate autorentals-support-testing module.

Code Under Test
<dependency>
    <groupId>info.ejava.assignments.testing.autorentals</groupId>
    <artifactId>autorentals-support-testing</artifactId>
    <version>${ejava.version}</version>
</dependency>

Your assignment will be implemented in a module you create and form a dependency on the implementation code.

3.2.3. Requirements

  1. Start with a dependency on supplied and completed RenterValidatorImpl and RenterDTO classes in the autorentals-support-testing module. You only need to understand and test them. You do not need to implement or modify anything being tested.

    1. RenterValidatorImpl implements a validateNewRenter method that returns a List<String> with identified validation error messages

    2. RenterDTO must have the following to be considered valid for registration:

      1. null id

      2. non-blank firstName and lastName

      3. dob older than minAge

  2. Implement a plain unit test case class for RenterValidatorImpl

    1. the test must be implemented without the use of a Spring context

    2. all instance variables for the test case must come from plain POJO calls

    3. tests must be implemented with JUnit 5 (Jupiter) constructs.

    4. tests must be implemented using AssertJ assertions. Either BDD or regular form of assertions is acceptable.

  3. The unit test case must have an init() method configured to execute "before each" test

    1. this can be used to initialize variables prior to each test

  4. The unit test case must have a test that verifies a valid RenterDTO will be reported as valid.

  5. The unit test case must have a test method that verifies an invalid RenterDTO will be reported as invalid with a string message for each error.

  6. Name the test so that it automatically gets executed by the Maven Surefire plugin.

3.2.4. Grading

Your solution will be evaluated on:

  1. write a test case and assertions using JUnit 5 "Jupiter" constructs

    1. whether you have implemented a pure unit test absent of any Spring context

    2. whether you have used JUnit 5 versus JUnit 4 constructs

    3. whether your init() method was configured to be automatically called "before each" test

    4. whether you have tested with a valid and invalid RenterDTO and verified results where appropriate

  2. leverage AssertJ assertion libraries

    1. whether you have used assertions to identify pass/fail

    2. whether you have used the AssertJ assertions

  3. execute tests using Maven Surefire plugin

    1. whether your unit test is executed by Maven surefire during a build

3.2.5. Additional Details

  1. A quick start project is available in autorentals-starter/assignment1-autorentals-testing, but

    1. copy the module into your own area

    2. modify at least the Maven groupId and Java package when used

  2. You are expected to form a dependency on the autorentals-support-testing module. The only things present in your src/main would be demonstration code that is supplied to you in the starter — but not part of any requirement.

3.3. Mocks

3.3.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of instantiating a Mock as part of unit testing. You will:

  1. implement a mock (using Mockito) into a JUnit unit test

  2. define custom behavior for a mock

  3. capture and inspect calls made to mocks by subjects under test

3.3.2. Overview

In this portion of the assignment, you are going to again implement a unit test case for a class and use a mock for one of its dependencies.

assignment1c autorentals testing mocks
Figure 8. Unit Testing with Mocks

3.3.3. Requirements

  1. Start with a dependency on supplied and completed RentersServiceImpl and other classes in the autorentals-support-testing module. You only need to understand and test them. You do not need to implement or modify anything being tested.

    1. RentersServiceImpl implements a createRenter method that

      1. validates the renter using a RenterValidator instance

      2. assigns the id if valid

      3. throws an exception with the error messages from the validator if invalid

  2. Implement a unit test case for the RentersService to verify validation for a valid and invalid RenterDTO

    1. the test case must be implemented without the use of a Spring context

    2. all instance variables for the test case, except for the mock, must come from plain POJO calls

    3. tests must be implemented using AssertJ assertions. Either BDD or regular form of assertions is acceptable.

    4. a Mockito Mock must be used for the RenterValidator instance. You may not use the RenterValidatorImpl class as part of this test

  3. The unit test case must have an init() method configured to run "before each" test and initialize the RentersServiceImpl with the Mock instance for RenterValidator.

  4. The unit test case must have a test that verifies a valid registration will be handled as valid.

    1. configure the Mock to return an empty List<String> when asked to validate the renter.

      Understand how the default Mock behaves before going too far with this.
    2. programmatically verify the Mock was called to validate the RenterDTO as part of the test criteria

  5. The unit test case must have a test method that verifies an invalid registration will be reported with an exception.

    1. configure the Mock to return a List<String> with errors for the renter

    2. programmatically verify the Mock was called 1 time to validate the RenterDTO as part of the test criteria

  6. Name the test so that it automatically gets executed by the Maven Surefire plugin.

This assignment is not to test the Mock. It is a test of the Subject using a Mock
You are not testing or demonstrating the Mock. Assume the Mock works and use the capabilities of the Mock to test the subject(s) they are injected into. Place any experiments with the Mock in a separate Test Case and keep this assignment focused on testing the subject (with the functioning Mock).

3.3.4. Grading

Your solution will be evaluated on:

  1. implement a mock (using Mockito) into a JUnit unit test

    1. whether you used a Mock to implement the RenterValidator as part of this unit test

    2. whether you used a Mockito Mock

    3. whether your unit test is executed by Maven surefire during a build

  2. define custom behavior for a mock

    1. whether you successfully configured the Mock to return an empty collection for the valid renter

    2. whether you successfully configured the Mock to return a collection of error messages for the invalid renter

  3. capture and inspect calls made to mocks by subjects under test

    1. whether you programmatically checked that the Mock validation method was called as a part of registration using Mockito library calls

3.3.5. Additional Details

  1. This portion of the assignment is expected to primarily consist of one additional test case added to the src/test tree.

  2. You may use BDD or non-BDD syntax for this test case and tests.

3.4. Mocked Unit Integration Test

3.4.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of implementing a unit integration test using a Spring context and Mock beans. You will:

  1. to implement unit integration tests within Spring Boot

  2. implement mocks (using Mockito) into a Spring context for use with unit integration tests

3.4.2. Overview

In this portion of the assignment, you are going to implement an injected Mock bean that will be injected by Spring into both the RentersServiceImpl @Component for operational functionality and the unit integration test for configuration and inspection commands.

assignment1c autorentals app mocked ntest
Figure 9. Mocked Unit Integration Test

3.4.3. Requirements

  1. Start with a supplied, completed, and injectable 'RentersServiceImpl' versus instantiating one like you did in the pure unit tests.

  2. Implement a unit integration test for the RentersService for a valid and invalid RenterDTO

    1. the test must be implemented using a Spring context

    2. all instance variables for the test case must come from injected components — even trivial ones.

    3. the RenterValidator must be implemented as Mockito Mock/Spring bean and injected into both the RenterValidatorImpl @Component and accessible in the unit integration test. You may not use the RenterValidatorImpl class as part of this test.

    4. define and inject a RenterDTO for a valid renter as an example of a bean that is unique to the test. This can come from a @Bean factory

  3. The unit integration test case must have a test that verifies a valid registration will be handled as valid.

  4. The unit integration test case must have a test method that verifies an invalid registration will be reported with an exception.

  5. Name the unit integration test so that it automatically gets executed by the Maven Surefire plugin.

3.4.4. Grading

Your solution will be evaluated on:

  1. to implement unit integration tests within Spring Boot

    1. whether you implemented a test case that instantiated a Spring context

    2. whether the subject(s) and their dependencies were injected by the Spring context

    3. whether the test case verified the requirements for a valid and invalid input

    4. whether your unit test is executed by Maven surefire during a build

  2. implement mocks (using Mockito) into a Spring context for use with unit integration tests

    1. whether you successfully declared a Mock bean that was injected into the necessary components under test and the test case for configuration

3.4.5. Additional Details

  1. This portion of the assignment is expected to primarily consist of

    1. adding one additional test case added to the src/test tree

    2. adding any supporting @TestConfiguration or other artifacts required to define the Spring context for the test

    3. changing the Mock to work with the Spring context

  2. Anything you may have been tempted to simply instantiate as private X x = new X(); can be changed to an injection by adding a @(Test)Configuration/@Bean factory to support testing. The point of having the 100% injection requirement is to encourage encapsulation and reuse among Test Cases for all types of test support objects.

  3. You may add the RentersTestConfiguration to the Spring context using either of the two annotation properties

    1. @SpringBootTest.classes

    2. @Import.value

  4. You may want to experiment with applying @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) versus the default @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) to you injected Renter and generate a random name in the @Bean factory. Every injected SCOPE_SINGLETON (default) gets the same instance. SCOPE_PROTOTYPE gets a separate instance. Useful to know, but not a graded part of the assignment.

3.5. Unmocked/BDD Unit Integration Testing

3.5.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of conducting an end-to-end unit integration test that is completely integrated with the Spring context and using Behavior Driven Design (BDD) syntax. You will:

  1. make use of BDD acceptance test keywords

3.5.2. Overview

In this portion of the assignment, you are going to implement an end-to-end unit integration test case for two classes integrated/injected using the Spring context with the syntactic assistance of BDD-style naming.

assignment1c autorentals app ntest
Figure 10. Unmocked/BDD Integration Testing

3.5.3. Requirements

  1. Start with a supplied, completed, and injectable RentersServiceImpl by creating a dependency on the autorentals-support-testing module. There are to be no POJO or Mock implementations of any classes under test.

  2. Implement a unit integration test for the RentersService for a valid and invalid RenterDTO

    1. the test must be implemented using a Spring context

    2. all instance variables for the test case must come from injected components

    3. the RenterValidator must be injected into the RentersServiceImpl using the Spring context. Your test case will not need access to that component.

    4. define and inject a RenterDTO for a valid renter as an example of a bean that is unique to the test. This can come from a @Bean factory from a Test Configuration

  3. The unit integration test case must have

    1. a display name defined for this test case that includes spaces

    2. a display name generation policy for contained test methods that includes spaces

  4. The unit integration test case must have a test that verifies a valid registration will be handled as valid.

    1. use BDD (then()) alternative syntax for AssertJ assertions

  5. The unit integration test case must have a test method that verifies an invalid registration will be reported with an exception.

    1. use BDD (then()) alternative syntax for AssertJ assertions

  6. Name the unit integration test so that it automatically gets executed by the Maven Surefire plugin.

3.5.4. Grading

Your solution will be evaluated on:

  1. make use of BDD acceptance test keywords

    1. whether you provided a custom display name for the test case that included spaces

    2. whether you provided a custom test method naming policy that included spaces

    3. whether you used BDD syntax for AssertJ assertions

  2. whether components are injected from Spring context into test

  3. whether this test is absent of any Mocks

3.5.5. Additional Details

  1. This portion of the assignment is expected to primarily consist of adding a test case that

    1. is based on the Mocked Unit Integration Test solution, which relies primarily on the beans of the Spring context

    2. removes any Mocks

    3. defines a names and naming policies for JUnit

    4. changes AssertJ syntax to BDD form

  2. The "custom test method naming policy" can be set using either an @Annotation or property. The properties approach has the advantage of being global to all tests within the module.