-
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
orejava-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.
|-- 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:
-
implement a service interface and implementation component
-
package a service within a Maven module separate from the application module
-
implement a Maven module dependency to make the component class available to the application module
-
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.
1.1.3. Requirements
-
Create an interface module with
-
a
RentalDTO
class withname
property. This is a simple data class. -
a
RentalsService
interface with agetRandomRental()
method. This method returns a singleRentalDTO
instance.
-
-
Create an AutoRental implementation module with
-
AutoRental implementation implementing the
RentalsService
interface that returns aRentalDTO
with a name (e.g., "autoRental0").
-
-
Create an application module with
-
a class that
-
implements
CommandLineRunner
interface -
has the
RentalsService
component injected using constructor injection -
a
run()
method that-
calls the
RentalsService
for a random AutoRentalDTO -
prints a startup message with the DTO name
-
relies on a
@Bean
factory to register it with the container and not a@Component
mechanism
-
-
-
a
@Configuration
class with two@Bean
factory methods-
one
@Bean
factory method to instantiate aRentalsService
autoRental implementation -
one
@Bean
factory method to instantiate theAppCommand
injected with aRentalsService
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.
-
-
a
@SpringBootApplication
class that initializes the Spring Context — which will process the@Configuration
class
-
-
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:
-
implement a service interface and implementation component
-
whether an interface module was created to contain interface and data dependencies of that interface
-
whether an implementation module was created to contain a class implementation of the interface
-
-
package a service within a Maven module separate from the application module
-
whether an application module was created to house a
@SpringBootApplication
and@Configuration
set of classes
-
-
implement a Maven module dependency to make the component class available to the application module
-
whether at least three separate Maven modules were created with a one-way dependency between them
-
-
use a
@Bean
factory method of a@Configuration
class to instantiate Spring-managed components-
whether the
@Configuration
class successfully instantiates theRentalsService
component -
whether the
@Configuration
class successfully instantiates theAppCommand
component injected with aRentalsService
component.
-
1.1.5. Additional Details
-
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. -
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:
-
implement value injection into a Spring Component
-
define a default value for the injection
-
specify property files from different locations
-
specify a property file for a basename
-
specify properties based on an active profile
-
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.
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 defaultapplication.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 Classpublic 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 Classpublic 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. |
-
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>
-
Add a @SpringBootApplication class with main() (provided in starter)
Your @SpringBootApplication Classpackage info.ejava_student.starter.assignment1.propertysource.rentals; import info.ejava.assignments.propertysource.rentals.PropertyCheck; @SpringBootApplication public class PropertySourceApp {
-
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 Filessrc/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 runtime2 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 fileapplication-dev.yml and application-site2.yml must be expressed using YAML syntax -
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 {
-
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 -
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., sourcesrc/locations
).Complete the following 5 scenarios:
-
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
becomesdefaultUser:defaultPass@defaulthost:27027
. -
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
-
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. -
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
-
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.
-
-
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.
-
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:
-
implement value injection into a Spring Component
-
whether
@Component
attributes were injected with values from property sources
-
-
define a default value for the injection
-
whether default values were correctly accepted or overridden
-
whether each unique property value was expressed in a single source file and property file inheritance was used when common values were needed
-
-
specify property files from different locations
-
whether your solution provides property values coming from multiple file locations
-
any property value that does not contain a developer/site-specific value (e.g.,
defaultUser
anddefaultPass
) must be provided by a property file within the JAR -
any property value that contains developer/site-specific values (e.g.,
dev1pass
andsite1pass
) must be provided by a property file outside of the JAR
-
-
the given
application.properties
file may not be modified -
named
.properties
files are supplied as properties files -
named
.yml
(i.e.,application-dev.yml
) files are supplied as YAML files
-
-
specify properties based on an active profile
-
whether your output reflects current values for
dev1
,dev2
,site1
, andsite2
profiles
-
-
specify both straight properties and YAML property file sources
-
whether your solution correctly supplies values for at least 1 properties file
-
whether your solution correctly supplies values for at least 1 YAML file
-
1.2.5. Additional Details
-
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. -
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. -
An integration unit test (
PropertySourceTest
) is provided within the support module that can automate the verifications. -
Ungraded Question to Ponder: How could you at runtime, provide a parameter option to the application to make the following output appear?
Alternate OutputconfigName=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:
-
map a Java @ConfigurationProperties class to a group of properties
-
create a read-only @ConfigurationProperties class using @ConstructorBinding
-
define a Jakarta EE Java validation rule for a property and have the property validated at runtime
-
generate boilerplate JavaBean methods using Lombok library
-
map nested properties to a @ConfigurationProperties class
-
reuse a @ConfigurationProperties class to map multiple property trees of the same structure
-
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.
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
-
Given the following read-only property classes, application.yml file, and
@Component
…-
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.
-
application.yml
YAML filerentals: 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. -
@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.
-
-
When running the application, a
@ConfigurationProperties
beans will be created to represent the contents of the YAML file as two separateList<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
. -
Configure your solution so that the
BoatRentalProperties
bean is injected into thePropertyPrinter
component along with the List of auto and toolRentalProperties
. 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<>(); } }
-
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:
-
map a Java @ConfigurationProperties class to a group of properties
-
whether Java classes were used to map values from the given YAML file
-
-
create a read-only @ConfigurationProperties class using @ConstructorBinding
-
whether read-only Java classes, using
@ConstructorBinding
were used to map values from the given YAML file
-
-
generate boilerplate JavaBean methods using Lombok library
-
whether lombok annotations were used to generate boilerplate Java bean code
-
-
map nested properties to a @ConfigurationProperties class
-
whether nested Java classes were used to map nested properties from a given YAML file
-
-
reuse a @ConfigurationProperties class to map multiple property trees of the same structure
-
whether multiple property trees were instantiated using the same Java classes
-
-
use @Qualifier annotation and other techniques to map or disambiguate an injection
-
whether multiple
@ConfigurationProperty
beans of the same type could be injected into a@Component
using a disambiguating technique.
-
1.3.5. Additional Details
-
A starter project is available in
autorentals-starter/assignment1-autorentals-configprops
. Modify Maven groupId and Java package if used.
-
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. -
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 { }
-
Ungraded Question to Ponder: What change(s) could be made to the application to validate the properties and report the following error?
Alternate OutputBinding 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:
-
Create a
@Configuration
class or@Bean
factory method to be registered based on the result of a condition at startup -
Create a Spring Boot Auto-configuration module to use as a "Starter"
-
Bootstrap Auto-configuration classes into applications using an
… AutoConfiguration.imports
metadata file -
Create a conditional component based on the presence of a property value
-
Create a conditional component based on a missing component
-
Create a conditional component based on the presence of a class
-
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.
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. |
-
Create a RentalsService interface module (already completed for beanfactory)
-
Add an interface to return a random rental as a
RentalDTO
instance
-
-
Create a AutoRentals implementation implementation module (already completed for beanfactory)
-
Add an implementation of the interface to return a RentalDTO with "auto" in the name property.
-
-
Create a ToolRentals implementation implementation module (new)
-
Add an implementation of the interface to return a RentalDTO with "tool" in the name property.
-
-
Create an Application Module with a
@SpringBootApplication
class-
Add a
CommandLineRunner
implementation class that gets injected with aRentalsService
bean and prints "Rentals has started" with the name of rental coming from the injected bean.-
Account for a null implementation injected when there is no implementation such that it still quietly prints the state of the component.
-
Include an injection (by any means) for properties
rentals.active
andrentals.preference
to print their values. Account for when they are not supplied.
-
-
Add a
@Bean
factory for theCommandLineRunner
implementation class — registered as "appCommand".-
Make the injection of the RentalsService optional to account for when there is no implementation
@Autowired(required=false)
-
-
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 theRentalsService
in the Application module, added a ToolRental implementation option, and removed a few Maven module dependencies.
-
-
Create a Rental starter Module
-
Add a dependency on the RentalsService interface module
-
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.
-
Add three conditional Auto-configuration classes
-
one that provides a
@Bean
factory for the ToolRentalsService implementation class-
Make this conditional on the presence of the ToolRental class(es) being available on the classpath
-
-
one that provides a
@Bean
factory for the AutoRentalsService implementation class-
Make this conditional on the presence of the AutoRental class(es) being available on the classpath
-
-
A third that provides another
@Bean
factory for the ToolRental implementation class-
Make this conditional on the presence of the ToolRental class(es) being available on the classpath
-
Make this also conditional on the property
rentals.preference
having the value oftools
.
-
-
-
Set the following priorities for the Auto-configuration classes
-
make the ToolRental/property (3rd from above) the highest priority
-
make the AutoRental factory the next highest priority
-
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.
-
-
Disable all RentalsService implementation
@Bean
factories if the propertyrentals.active
is present and has the valuefalse
Treat false
as being not the valuetrue
. Spring Boot does not offer a disable condition, so you will be looking to enable when the property istrue
or missing. -
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.
-
-
Augment the Application module pom to address dependencies
-
Add a dependency on the Starter Module
-
Create a profile (
autos
) that adds a direct dependency on the AutoRentals implementation module. The "assignment starter" provides an example of this. -
Create a profile (
tools
) that adds a direct dependency on the ToolRentals implementation module.
-
-
Verify your solution will determine its results based on the available classes and properties at runtime. Your solution must have the following behavior
-
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 -
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 -
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 -
autos
andtools
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 -
autos
andtools
Maven profiles active and Spring propertyRental.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 withautos
value3 ToolRentalsService implementation was injected because of preference specified -
autos
andtools
Maven profiles active and Spring propertyrentals.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 withfalse
value3 no implementation was injected because feature deactivated with property value
-
-
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:
-
Create a
@Configuration
class/@Bean
factory method to be registered based on the result of a condition at startup-
whether your solution provides the intended implementation class based on the runtime environment
-
-
Create a Spring Boot Auto-configuration module to use as a "Starter"
-
whether you have successfully packaged your
@Configuration
classes as Auto-Configuration classes outside the package scanning of the@SpringBootApplication
-
-
Bootstrap Auto-configuration classes into applications using a
AutoConfiguration.imports
metadata file-
whether you have bootstrapped your Auto-Configuration classes so they are processed by Spring Boot at application startup
-
-
Create a conditional component based on the presence of a property value
-
whether you activate or deactivate a
@Bean
factory based on the presence or absence of a specific the a specific property
-
-
Create a conditional component based on a missing component
-
whether you activate or deactivate a
@Bean
factory based on the presence or absence of a specific@Component
-
-
Create a conditional component based on the presence of a class
-
whether you activate or deactivate a
@Bean
factory based on the presence or absence of a class -
whether your starter causes unnecessary dependencies on the Application module
-
-
Define a processing dependency order for Auto-configuration classes
-
whether your solution is capable of implementing the stated priorities of which bean implementation to instantiate under which conditions
-
1.4.5. Additional Details
-
A starter project is available in
autorentals-starter/assignment1-autorentals-autoconfig
. Modify Maven groupId and Java package if used. -
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:
-
obtain access to an SLF4J Logger
-
issue log events at different severity levels
-
format log events for regular parameters
-
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.
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. |
-
Create several components in different Java sub-packages (app, svc, and repo)
-
an
AppCommand
component class in theapp
Java sub-package -
a
AutoRentalsServiceImpl
component class in thesvc
Java sub-package -
a
AutoRentalsHelperImpl
component class in thesvc
Java sub-package -
a
AutoRentalsRepositoryImpl
component class in therepo
Java sub-package
-
-
Implement a chain of calls from the
AppCommand
@Component
run() method through the other components.Figure 4. Required Call Sequence-
AppCommand.run() logs message with text "AutoRentals has started"
-
AppCommand.run() calls ServiceImpl.calcDelta(autoId, renterId) with a
autoId
andrenterId
to determine a delta between the two instances -
ServiceImpl.calcDelta(autoId, renterId) calls RepositoryImpl (getLeaderByAutoId(autoId) and getByRenterId(renterId)) to get
AutoRentalDTOs
-
RepositoryImpl can create transient instances with provided Ids and fake remaining properties
-
-
ServiceImpl.calcDelta(autoId, renterId) also calls ResultsHelper.calcDelta() to get a delta between the two
AutoRentalDTOs
-
HelperImpl.calcDelta(leader, target) calls AutoRentalDTO.getAmount() on the two provided
AutoRentalDTO
instances to determine the deltaThe 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.
-
-
Implement a
toString()
method inAutoRentalDTO
that includes theautoId
,renterId
, andamount
information. -
Instantiate an SLF4J
Logger
into each of the four components-
manually instantiate a static final
Logger
with the name "X.Y" inAppCommand
-
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
-
-
Implement logging statements in each of the methods
-
the severity of RepositoryImpl logging events are all TRACE
-
the severity of HelperImpl.calcDelta() logging events are DEBUG and TRACE (there must be at least two — one of each and no other levels)
-
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)
-
the severity of AppCommand logging events are all INFO (and no other levels)
-
-
Output available results information in log statements
-
Leverage the SLF4J parameter formatting syntax when implementing the log
-
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 statementsi.e., autoRental.getAutoId(), autoRental.getRenterId(), etc. -
For each of the TRACE statements, use the inferred
AutoRentalDTO.toString()
method to log the AutoRentalDTO.Use inferred toString() on passed object on TRACE statementsi.e., log.debug("…", autoRental) — no direct calls totoString()
-
-
Supply two profiles
-
the root logger must be turned off by default (e.g., in
application.properties
) -
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" -
a
repo-only
profile that turns on only log statements from the repo class(es).
-
-
Wrap your solution in a Maven build that executes the JAR three times with:
-
(no profile) - no logs should be produced
-
app-debug
profile-
DEBUG and higher priority logging events from the application (including "X.Y") are output to console
-
no TRACE messages are output to the console
-
-
repo-only
profile-
logging events from repository class(es) are output to the console
-
no other logging events are output to the console
-
-
2.1.4. Grading
Your solution will be evaluated on:
-
obtain access to an SLF4J Logger
-
whether you manually instantiated a Logger into the AppCommand
@Component
-
whether you leveraged Lombok to instantiate a Logger into the other
@Components
-
whether your App Command
@Component
logger was named "X.Y" -
whether your other
@Component
loggers were named after the package/class they were declared in
-
-
issue log events at different severity levels
-
where logging statements issued at the specified verbosity levels
-
-
format log events for regular parameters
-
whether SLF4J format statements were used when including variable information
-
-
filter log events based on source and severity thresholds
-
whether your profiles set the logging levels appropriately to only output the requested logging events
-
2.1.5. Other Details
-
You may use any means to instantiate/inject the components (i.e.,
@Bean
factories or@Component
annotations) -
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
. -
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
andtrace
Modify Maven groupId and Java package if used.
-
-
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:
-
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. |
2.2.3. Requirements
-
Update the
toString()
method inAutoRentalDTO
to be expensive to call-
artificially insert a 750 milliseconds delay within the
toString()
call
-
-
Refactor your log statements, if required, to only call
toString()
when TRACE is active-
leverage the SLF4J API calls to make that as simple as possible
-
2.2.4. Grading
Your solution will be evaluated on:
-
efficiently bypass log statements that do not meet criteria
-
whether your
toString()
method paused the calling thread for 750 milliseconds when TRACE threshold is activated -
whether the calls to
toString()
are bypassed when priority threshold is set higher than TRACE -
the simplicity of your solution
-
2.2.5. Other Details
-
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.
-
The
app-debug
should not exhibit any additional delays. Therepo-only
should exhibit a 1.5 (2x750msec) second delay. -
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:
-
filter log events based on source and severity thresholds
-
customize log patterns
-
customize appenders
-
add contextual information to log events using Mapped Diagnostic Context
-
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. |
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
-
Declare two Appenders as part of a custom Logback configuration
-
CONSOLE appender to output to stdout
-
FILE appender to output to a file
target/logs/appenders.log
-
-
Assign the Appenders to Loggers
-
root logger events must be assigned to the CONSOLE Appender
-
any log events issued to the "X.Y" Logger must be assigned to both the CONSOLE and FILE Appenders
-
any log events issued to the "…svc" Logger must also be assigned to both the CONSOLE and FILE Appenders
-
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.
-
-
Add an
appenders
profile that-
automatically enacts the requirements above
-
sets a base of INFO severity and up for all loggers with your application
-
-
Add a
requestId
property to the Mapped Diagnostic Context (MDC)Figure 6. Initialize Mapped Diagnostic Context (MDC)-
generate a random/changing value using a 36 character UUID String
Example: UUID.randomUUID().toString()
⇒d587d04c-9047-4aa2-bfb3-82b25524ce12
-
insert the value prior to the first logging statement — in the
AppCommand
component -
remove the MDC property when processing is complete within the
AppCommand
component
-
-
Declare a custom logging pattern in the
appenders
profile that includes the MDCrequestId
value in each log statements written by the FILE Appender-
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 easilyExample: [d587d04c-9047-4aa2-bfb3-82b25524ce12]
-
The MDC
requestId
is not output by the CONSOLE Appender
-
-
Add an additional
trace
profile that-
activates logging events at TRACE severity and up for all loggers with your application
-
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.
-
-
Apply the
appenders
profile to-
output logging events at INFO severity and up to both CONSOLE and FILE Appenders
-
include the MDC
requestId
in events logged by the FILE Appender -
not include method and line number information in events logged
-
-
Apply the
appenders
andtrace
profiles to-
output logging events at TRACE severity and up to both CONSOLE and FILE Appenders
-
continue to include the MDC
requestId
in events logged by the FILE Appender -
add method and line number information in events logged by the FILE Appender
-
2.3.4. Grading
Your solution will be evaluated on:
-
filter log events based on source and severity thresholds
-
whether your log events from the different Loggers were written to the required appenders
-
whether a log event instance appeared at most once per appender
-
-
customize log patterns
-
whether your FILE Appender output was augmented with the
requestId
whenappenders
profile was active -
whether your FILE Appender output was augmented with method and line number information when
trace
profile was active
-
-
customize appenders
-
whether a FILE and CONSOLE appender were defined
-
whether a custom logging pattern was successfully defined for the FILE Logger
-
-
add contextual information to log events using Mapped Diagnostic Context
-
whether a
requestId
was added to the Mapped Data Context (MDC) -
whether the
requestId
was included in the customized logging pattern for the FILE Appender when theappenders
profile was active
-
-
use Spring Profiles to conditionally configure logging
-
whether your required logging configurations where put in place when activating the
appenders
profile -
whether your required logging configurations where put in place when activating the
appenders
andtrace
profiles
-
2.3.5. Other Details
-
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"/> ...
-
Your
appenders
andtrace
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. -
The following snippet shows an example resulting logfile from when
appenders
and thenappenders,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 whenappenders
profile active2 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 whenappenders
profile active2 method and line number info are supplied -
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.
$ 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 Implementing CommandLineRunner as a Class that Implements Interface
Returning a Lambda Function that Implements Interface
|
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:
-
write a test case and assertions using JUnit 5 "Jupiter" constructs
-
leverage the AssertJ assertion library
-
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.
The code under test is 100% complete and provided to you in a separate autorentals-support-testing
module.
<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
-
Start with a dependency on supplied and completed
RenterValidatorImpl
andRenterDTO
classes in theautorentals-support-testing
module. You only need to understand and test them. You do not need to implement or modify anything being tested.-
RenterValidatorImpl
implements avalidateNewRenter
method that returns aList<String>
with identified validation error messages -
RenterDTO
must have the following to be considered valid for registration:-
null
id
-
non-blank
firstName
andlastName
-
dob
older than minAge
-
-
-
Implement a plain unit test case class for
RenterValidatorImpl
-
the test must be implemented without the use of a Spring context
-
all instance variables for the test case must come from plain POJO calls
-
tests must be implemented with JUnit 5 (Jupiter) constructs.
-
tests must be implemented using AssertJ assertions. Either BDD or regular form of assertions is acceptable.
-
-
The unit test case must have an
init()
method configured to execute "before each" test-
this can be used to initialize variables prior to each test
-
-
The unit test case must have a test that verifies a valid RenterDTO will be reported as valid.
-
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.
-
Name the test so that it automatically gets executed by the Maven Surefire plugin.
3.2.4. Grading
Your solution will be evaluated on:
-
write a test case and assertions using JUnit 5 "Jupiter" constructs
-
whether you have implemented a pure unit test absent of any Spring context
-
whether you have used JUnit 5 versus JUnit 4 constructs
-
whether your
init()
method was configured to be automatically called "before each" test -
whether you have tested with a valid and invalid
RenterDTO
and verified results where appropriate
-
-
leverage AssertJ assertion libraries
-
whether you have used assertions to identify pass/fail
-
whether you have used the AssertJ assertions
-
-
execute tests using Maven Surefire plugin
-
whether your unit test is executed by Maven surefire during a build
-
3.2.5. Additional Details
-
A quick start project is available in
autorentals-starter/assignment1-autorentals-testing
, but-
copy the module into your own area
-
modify at least the Maven groupId and Java package when used
-
-
You are expected to form a dependency on the
autorentals-support-testing
module. The only things present in yoursrc/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:
-
implement a mock (using Mockito) into a JUnit unit test
-
define custom behavior for a mock
-
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.
3.3.3. Requirements
-
Start with a dependency on supplied and completed
RentersServiceImpl
and other classes in theautorentals-support-testing
module. You only need to understand and test them. You do not need to implement or modify anything being tested.-
RentersServiceImpl
implements acreateRenter
method that-
validates the renter using a
RenterValidator
instance -
assigns the
id
if valid -
throws an exception with the error messages from the validator if invalid
-
-
-
Implement a unit test case for the
RentersService
to verify validation for a valid and invalidRenterDTO
-
the test case must be implemented without the use of a Spring context
-
all instance variables for the test case, except for the mock, must come from plain POJO calls
-
tests must be implemented using AssertJ assertions. Either BDD or regular form of assertions is acceptable.
-
a Mockito Mock must be used for the
RenterValidator
instance. You may not use theRenterValidatorImpl
class as part of this test
-
-
The unit test case must have an
init()
method configured to run "before each" test and initialize theRentersServiceImpl
with the Mock instance forRenterValidator
. -
The unit test case must have a test that verifies a valid registration will be handled as valid.
-
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. -
programmatically verify the Mock was called to validate the
RenterDTO
as part of the test criteria
-
-
The unit test case must have a test method that verifies an invalid registration will be reported with an exception.
-
configure the Mock to return a
List<String>
with errors for the renter -
programmatically verify the Mock was called 1 time to validate the
RenterDTO
as part of the test criteria
-
-
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:
-
implement a mock (using Mockito) into a JUnit unit test
-
whether you used a Mock to implement the
RenterValidator
as part of this unit test -
whether you used a Mockito Mock
-
whether your unit test is executed by Maven surefire during a build
-
-
define custom behavior for a mock
-
whether you successfully configured the Mock to return an empty collection for the valid renter
-
whether you successfully configured the Mock to return a collection of error messages for the invalid renter
-
-
capture and inspect calls made to mocks by subjects under test
-
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
-
This portion of the assignment is expected to primarily consist of one additional test case added to the
src/test
tree. -
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:
-
to implement unit integration tests within Spring Boot
-
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.
3.4.3. Requirements
-
Start with a supplied, completed, and injectable 'RentersServiceImpl' versus instantiating one like you did in the pure unit tests.
-
Implement a unit integration test for the
RentersService
for a valid and invalidRenterDTO
-
the test must be implemented using a Spring context
-
all instance variables for the test case must come from injected components — even trivial ones.
-
the
RenterValidator
must be implemented as Mockito Mock/Spring bean and injected into both theRenterValidatorImpl
@Component
and accessible in the unit integration test. You may not use theRenterValidatorImpl
class as part of this test. -
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
-
-
The unit integration test case must have a test that verifies a valid registration will be handled as valid.
-
The unit integration test case must have a test method that verifies an invalid registration will be reported with an exception.
-
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:
-
to implement unit integration tests within Spring Boot
-
whether you implemented a test case that instantiated a Spring context
-
whether the subject(s) and their dependencies were injected by the Spring context
-
whether the test case verified the requirements for a valid and invalid input
-
whether your unit test is executed by Maven surefire during a build
-
-
implement mocks (using Mockito) into a Spring context for use with unit integration tests
-
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
-
This portion of the assignment is expected to primarily consist of
-
adding one additional test case added to the
src/test
tree -
adding any supporting
@TestConfiguration
or other artifacts required to define the Spring context for the test -
changing the Mock to work with the Spring context
-
-
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. -
You may add the
RentersTestConfiguration
to the Spring context using either of the two annotation properties-
@SpringBootTest.classes
-
@Import.value
-
-
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 injectedSCOPE_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:
-
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.
3.5.3. Requirements
-
Start with a supplied, completed, and injectable
RentersServiceImpl
by creating a dependency on theautorentals-support-testing
module. There are to be no POJO or Mock implementations of any classes under test. -
Implement a unit integration test for the
RentersService
for a valid and invalidRenterDTO
-
the test must be implemented using a Spring context
-
all instance variables for the test case must come from injected components
-
the
RenterValidator
must be injected into theRentersServiceImpl
using the Spring context. Your test case will not need access to that component. -
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
-
-
The unit integration test case must have
-
a display name defined for this test case that includes spaces
-
a display name generation policy for contained test methods that includes spaces
-
-
The unit integration test case must have a test that verifies a valid registration will be handled as valid.
-
use BDD (
then()
) alternative syntax for AssertJ assertions
-
-
The unit integration test case must have a test method that verifies an invalid registration will be reported with an exception.
-
use BDD (
then()
) alternative syntax for AssertJ assertions
-
-
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:
-
make use of BDD acceptance test keywords
-
whether you provided a custom display name for the test case that included spaces
-
whether you provided a custom test method naming policy that included spaces
-
whether you used BDD syntax for AssertJ assertions
-
-
whether components are injected from Spring context into test
-
whether this test is absent of any Mocks
3.5.5. Additional Details
-
This portion of the assignment is expected to primarily consist of adding a test case that
-
is based on the Mocked Unit Integration Test solution, which relies primarily on the beans of the Spring context
-
removes any Mocks
-
defines a names and naming policies for JUnit
-
changes AssertJ syntax to BDD form
-
-
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.