* #1317 Add Special Case Pattern To focus on pattern itself, I implement DB and maintenance lock by the singleton instance. * #1317 Add special cases unit tests Assert the logger output (ref: https://stackoverflow.com/a/52229629) * #1317 Add README.md Add Special Case Pattern README * #1317 Format: add a new line to end of file Co-authored-by: Subhrodip Mohanta <subhrodipmohanta@gmail.com>
This commit is contained in:
parent
bbc4fdfc24
commit
dea7ecfb7a
1
pom.xml
1
pom.xml
@ -200,6 +200,7 @@
|
|||||||
<module>factory</module>
|
<module>factory</module>
|
||||||
<module>separated-interface</module>
|
<module>separated-interface</module>
|
||||||
<module>data-transfer-object-enum-impl</module>
|
<module>data-transfer-object-enum-impl</module>
|
||||||
|
<module>special-case</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
|
368
special-case/README.md
Normal file
368
special-case/README.md
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
---
|
||||||
|
layout: pattern
|
||||||
|
title: Special Case
|
||||||
|
folder: special-case
|
||||||
|
permalink: /patterns/special-case/
|
||||||
|
categories: Behavioral
|
||||||
|
tags:
|
||||||
|
- Extensibility
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
|
||||||
|
Define some special cases, and encapsulates them into subclasses that provide different special behaviors.
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
Real world example
|
||||||
|
|
||||||
|
> In an e-commerce system, presentation layer expects application layer to produce certain view model.
|
||||||
|
> We have a successful scenario, in which receipt view model contains actual data from the purchase,
|
||||||
|
> and a couple of failure scenarios.
|
||||||
|
|
||||||
|
In plain words
|
||||||
|
|
||||||
|
> Special Case pattern allows returning non-null real objects that perform special behaviors.
|
||||||
|
|
||||||
|
In [Patterns of Enterprise Application Architecture](https://martinfowler.com/books/eaa.html) says
|
||||||
|
the difference from Null Object Pattern
|
||||||
|
|
||||||
|
> If you’ll pardon the unresistable pun, I see Null Object as special case of Special Case.
|
||||||
|
|
||||||
|
**Programmatic Example**
|
||||||
|
|
||||||
|
To focus on the pattern itself, we implement DB and maintenance lock of the e-commerce system by the singleton instance.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Db {
|
||||||
|
private static Db instance;
|
||||||
|
private Map<String, User> userName2User;
|
||||||
|
private Map<User, Account> user2Account;
|
||||||
|
private Map<String, Product> itemName2Product;
|
||||||
|
|
||||||
|
public static Db getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (Db.class) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new Db();
|
||||||
|
instance.userName2User = new HashMap<>();
|
||||||
|
instance.user2Account = new HashMap<>();
|
||||||
|
instance.itemName2Product = new HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seedUser(String userName, Double amount) {
|
||||||
|
User user = new User(userName);
|
||||||
|
instance.userName2User.put(userName, user);
|
||||||
|
Account account = new Account(amount);
|
||||||
|
instance.user2Account.put(user, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seedItem(String itemName, Double price) {
|
||||||
|
Product item = new Product(price);
|
||||||
|
itemName2Product.put(itemName, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User findUserByUserName(String userName) {
|
||||||
|
if (!userName2User.containsKey(userName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return userName2User.get(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account findAccountByUser(User user) {
|
||||||
|
if (!user2Account.containsKey(user)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return user2Account.get(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Product findProductByItemName(String itemName) {
|
||||||
|
if (!itemName2Product.containsKey(itemName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return itemName2Product.get(itemName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class User {
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
public User(String userName) {
|
||||||
|
this.userName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReceiptDto purchase(Product item) {
|
||||||
|
return new ReceiptDto(item.getPrice());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Account {
|
||||||
|
private Double amount;
|
||||||
|
|
||||||
|
public Account(Double amount) {
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneyTransaction withdraw(Double price) {
|
||||||
|
if (price > amount) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new MoneyTransaction(amount, price);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getAmount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Product {
|
||||||
|
private Double price;
|
||||||
|
|
||||||
|
public Product(Double price) {
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MaintenanceLock {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(MaintenanceLock.class);
|
||||||
|
|
||||||
|
private static MaintenanceLock instance;
|
||||||
|
private boolean lock = true;
|
||||||
|
|
||||||
|
public static MaintenanceLock getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (MaintenanceLock.class) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new MaintenanceLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLock() {
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLock(boolean lock) {
|
||||||
|
this.lock = lock;
|
||||||
|
LOGGER.info("Maintenance lock is set to: " + lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's first introduce presentation layer, the receipt view model interface and its implementation of successful scenario.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface ReceiptViewModel {
|
||||||
|
void show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReceiptDto implements ReceiptViewModel {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ReceiptDto.class);
|
||||||
|
|
||||||
|
private Double price;
|
||||||
|
|
||||||
|
public ReceiptDto(Double price) {
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
LOGGER.info("Receipt: " + price + " paid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And here are the implementations of failure scenarios, which are the special cases.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class DownForMaintenance implements ReceiptViewModel {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(DownForMaintenance.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
LOGGER.info("Down for maintenance");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InvalidUser implements ReceiptViewModel {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(InvalidUser.class);
|
||||||
|
|
||||||
|
private final String userName;
|
||||||
|
|
||||||
|
public InvalidUser(String userName) {
|
||||||
|
this.userName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
LOGGER.info("Invalid user: " + userName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OutOfStock implements ReceiptViewModel {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(OutOfStock.class);
|
||||||
|
|
||||||
|
private String userName;
|
||||||
|
private String itemName;
|
||||||
|
|
||||||
|
public OutOfStock(String userName, String itemName) {
|
||||||
|
this.userName = userName;
|
||||||
|
this.itemName = itemName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
LOGGER.info("Out of stock: " + itemName + " for user = " + userName + " to buy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InsufficientFunds implements ReceiptViewModel {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(InsufficientFunds.class);
|
||||||
|
|
||||||
|
private String userName;
|
||||||
|
private Double amount;
|
||||||
|
private String itemName;
|
||||||
|
|
||||||
|
public InsufficientFunds(String userName, Double amount, String itemName) {
|
||||||
|
this.userName = userName;
|
||||||
|
this.amount = amount;
|
||||||
|
this.itemName = itemName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
LOGGER.info("Insufficient funds: " + amount + " of user: " + userName
|
||||||
|
+ " for buying item: " + itemName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Second, here's the application layer, the application services implementation and the domain services implementation.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class ApplicationServicesImpl implements ApplicationServices {
|
||||||
|
private DomainServicesImpl domain = new DomainServicesImpl();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReceiptViewModel loggedInUserPurchase(String userName, String itemName) {
|
||||||
|
if (isDownForMaintenance()) {
|
||||||
|
return new DownForMaintenance();
|
||||||
|
}
|
||||||
|
return this.domain.purchase(userName, itemName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDownForMaintenance() {
|
||||||
|
return MaintenanceLock.getInstance().isLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DomainServicesImpl implements DomainServices {
|
||||||
|
public ReceiptViewModel purchase(String userName, String itemName) {
|
||||||
|
Db.User user = Db.getInstance().findUserByUserName(userName);
|
||||||
|
if (user == null) {
|
||||||
|
return new InvalidUser(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Db.Account account = Db.getInstance().findAccountByUser(user);
|
||||||
|
return purchase(user, account, itemName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReceiptViewModel purchase(Db.User user, Db.Account account, String itemName) {
|
||||||
|
Db.Product item = Db.getInstance().findProductByItemName(itemName);
|
||||||
|
if (item == null) {
|
||||||
|
return new OutOfStock(user.getUserName(), itemName);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReceiptDto receipt = user.purchase(item);
|
||||||
|
MoneyTransaction transaction = account.withdraw(receipt.getPrice());
|
||||||
|
if (transaction == null) {
|
||||||
|
return new InsufficientFunds(user.getUserName(), account.getAmount(), itemName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return receipt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, the client send requests the application services to get the presentation view.
|
||||||
|
|
||||||
|
```java
|
||||||
|
// DB seeding
|
||||||
|
LOGGER.info("Db seeding: " + "1 user: {\"ignite1771\", amount = 1000.0}, "
|
||||||
|
+ "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}");
|
||||||
|
Db.getInstance().seedUser("ignite1771", 1000.0);
|
||||||
|
Db.getInstance().seedItem("computer", 800.0);
|
||||||
|
Db.getInstance().seedItem("car", 20000.0);
|
||||||
|
|
||||||
|
var applicationServices = new ApplicationServicesImpl();
|
||||||
|
ReceiptViewModel receipt;
|
||||||
|
|
||||||
|
LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv");
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("abc123", "tv");
|
||||||
|
receipt.show();
|
||||||
|
MaintenanceLock.getInstance().setLock(false);
|
||||||
|
LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv");
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("abc123", "tv");
|
||||||
|
receipt.show();
|
||||||
|
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "tv");
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("ignite1771", "tv");
|
||||||
|
receipt.show();
|
||||||
|
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "car");
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("ignite1771", "car");
|
||||||
|
receipt.show();
|
||||||
|
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "computer");
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("ignite1771", "computer");
|
||||||
|
receipt.show();
|
||||||
|
```
|
||||||
|
|
||||||
|
Program output of every request:
|
||||||
|
|
||||||
|
```
|
||||||
|
Down for maintenance
|
||||||
|
Invalid user: abc123
|
||||||
|
Out of stock: tv for user = ignite1771 to buy
|
||||||
|
Insufficient funds: 1000.0 of user: ignite1771 for buying item: car
|
||||||
|
Receipt: 800.0 paid
|
||||||
|
```
|
||||||
|
|
||||||
|
## Class diagram
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Applicability
|
||||||
|
|
||||||
|
Use the Special Case pattern when
|
||||||
|
|
||||||
|
* You have multiple places in the system that have the same behavior after a conditional check
|
||||||
|
for a particular class instance, or the same behavior after a null check.
|
||||||
|
* Return a real object that performs the real behavior, instead of a null object that performs nothing.
|
||||||
|
|
||||||
|
## Tutorial
|
||||||
|
|
||||||
|
* [Special Case Tutorial](https://www.codinghelmet.com/articles/reduce-cyclomatic-complexity-special-case)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
* [How to Reduce Cyclomatic Complexity Part 2: Special Case Pattern](https://www.codinghelmet.com/articles/reduce-cyclomatic-complexity-special-case)
|
||||||
|
* [Patterns of Enterprise Application Architecture](https://martinfowler.com/books/eaa.html)
|
||||||
|
* [Special Case](https://www.martinfowler.com/eaaCatalog/specialCase.html)
|
119
special-case/etc/special-case.urm.puml
Normal file
119
special-case/etc/special-case.urm.puml
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
@startuml
|
||||||
|
left to right direction
|
||||||
|
package com.iluwatar.specialcase {
|
||||||
|
class App {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
+ App()
|
||||||
|
+ main(args : String[]) {static}
|
||||||
|
}
|
||||||
|
interface ApplicationServices {
|
||||||
|
+ loggedInUserPurchase(String, String) : ReceiptViewModel {abstract}
|
||||||
|
}
|
||||||
|
class ApplicationServicesImpl {
|
||||||
|
- domain : DomainServicesImpl
|
||||||
|
+ ApplicationServicesImpl()
|
||||||
|
- isDownForMaintenance() : boolean
|
||||||
|
+ loggedInUserPurchase(userName : String, itemName : String) : ReceiptViewModel
|
||||||
|
}
|
||||||
|
class Db {
|
||||||
|
- instance : Db {static}
|
||||||
|
- itemName2Product : Map<String, Product>
|
||||||
|
- user2Account : Map<User, Account>
|
||||||
|
- userName2User : Map<String, User>
|
||||||
|
+ Db()
|
||||||
|
+ findAccountByUser(user : User) : Account
|
||||||
|
+ findProductByItemName(itemName : String) : Product
|
||||||
|
+ findUserByUserName(userName : String) : User
|
||||||
|
+ getInstance() : Db {static}
|
||||||
|
+ seedItem(itemName : String, price : Double)
|
||||||
|
+ seedUser(userName : String, amount : Double)
|
||||||
|
}
|
||||||
|
class Account {
|
||||||
|
- amount : Double
|
||||||
|
+ Account(this$0 : Double)
|
||||||
|
+ getAmount() : Double
|
||||||
|
+ withdraw(price : Double) : MoneyTransaction
|
||||||
|
}
|
||||||
|
class Product {
|
||||||
|
- price : Double
|
||||||
|
+ Product(this$0 : Double)
|
||||||
|
+ getPrice() : Double
|
||||||
|
}
|
||||||
|
class User {
|
||||||
|
- userName : String
|
||||||
|
+ User(this$0 : String)
|
||||||
|
+ getUserName() : String
|
||||||
|
+ purchase(item : Product) : ReceiptDto
|
||||||
|
}
|
||||||
|
interface DomainServices {
|
||||||
|
}
|
||||||
|
class DomainServicesImpl {
|
||||||
|
+ DomainServicesImpl()
|
||||||
|
- purchase(user : User, account : Account, itemName : String) : ReceiptViewModel
|
||||||
|
+ purchase(userName : String, itemName : String) : ReceiptViewModel
|
||||||
|
}
|
||||||
|
class DownForMaintenance {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
+ DownForMaintenance()
|
||||||
|
+ show()
|
||||||
|
}
|
||||||
|
class InsufficientFunds {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
- amount : Double
|
||||||
|
- itemName : String
|
||||||
|
- userName : String
|
||||||
|
+ InsufficientFunds(userName : String, amount : Double, itemName : String)
|
||||||
|
+ show()
|
||||||
|
}
|
||||||
|
class InvalidUser {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
- userName : String
|
||||||
|
+ InvalidUser(userName : String)
|
||||||
|
+ show()
|
||||||
|
}
|
||||||
|
class MaintenanceLock {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
- instance : MaintenanceLock {static}
|
||||||
|
- lock : boolean
|
||||||
|
+ MaintenanceLock()
|
||||||
|
+ getInstance() : MaintenanceLock {static}
|
||||||
|
+ isLock() : boolean
|
||||||
|
+ setLock(lock : boolean)
|
||||||
|
}
|
||||||
|
class MoneyTransaction {
|
||||||
|
- amount : Double
|
||||||
|
- price : Double
|
||||||
|
+ MoneyTransaction(amount : Double, price : Double)
|
||||||
|
}
|
||||||
|
class OutOfStock {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
- itemName : String
|
||||||
|
- userName : String
|
||||||
|
+ OutOfStock(userName : String, itemName : String)
|
||||||
|
+ show()
|
||||||
|
}
|
||||||
|
class ReceiptDto {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
- price : Double
|
||||||
|
+ ReceiptDto(price : Double)
|
||||||
|
+ getPrice() : Double
|
||||||
|
+ show()
|
||||||
|
}
|
||||||
|
interface ReceiptViewModel {
|
||||||
|
+ show() {abstract}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
User --+ Db
|
||||||
|
Product --+ Db
|
||||||
|
MaintenanceLock --> "-instance" MaintenanceLock
|
||||||
|
Db --> "-instance" Db
|
||||||
|
ApplicationServicesImpl --> "-domain" DomainServicesImpl
|
||||||
|
Account --+ Db
|
||||||
|
ApplicationServicesImpl ..|> ApplicationServices
|
||||||
|
DomainServicesImpl ..|> DomainServices
|
||||||
|
DownForMaintenance ..|> ReceiptViewModel
|
||||||
|
InsufficientFunds ..|> ReceiptViewModel
|
||||||
|
InvalidUser ..|> ReceiptViewModel
|
||||||
|
OutOfStock ..|> ReceiptViewModel
|
||||||
|
ReceiptDto ..|> ReceiptViewModel
|
||||||
|
@enduml
|
BIN
special-case/etc/special_case_urm.png
Normal file
BIN
special-case/etc/special_case_urm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 448 KiB |
22
special-case/pom.xml
Normal file
22
special-case/pom.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>java-design-patterns</artifactId>
|
||||||
|
<groupId>com.iluwatar</groupId>
|
||||||
|
<version>1.24.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>special-case</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
47
special-case/src/main/java/com/iluwatar/specialcase/App.java
Normal file
47
special-case/src/main/java/com/iluwatar/specialcase/App.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>The Special Case Pattern is a software design pattern that encapsulates particular cases
|
||||||
|
* into subclasses that provide special behaviors.</p>
|
||||||
|
*
|
||||||
|
* <p>In this example ({@link ReceiptViewModel}) encapsulates all particular cases.</p>
|
||||||
|
*/
|
||||||
|
public class App {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program entry point.
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// DB seeding
|
||||||
|
LOGGER.info("Db seeding: " + "1 user: {\"ignite1771\", amount = 1000.0}, "
|
||||||
|
+ "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}");
|
||||||
|
Db.getInstance().seedUser("ignite1771", 1000.0);
|
||||||
|
Db.getInstance().seedItem("computer", 800.0);
|
||||||
|
Db.getInstance().seedItem("car", 20000.0);
|
||||||
|
|
||||||
|
final var applicationServices = new ApplicationServicesImpl();
|
||||||
|
ReceiptViewModel receipt;
|
||||||
|
|
||||||
|
LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv");
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("abc123", "tv");
|
||||||
|
receipt.show();
|
||||||
|
MaintenanceLock.getInstance().setLock(false);
|
||||||
|
LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv");
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("abc123", "tv");
|
||||||
|
receipt.show();
|
||||||
|
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "tv");
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("ignite1771", "tv");
|
||||||
|
receipt.show();
|
||||||
|
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "car");
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("ignite1771", "car");
|
||||||
|
receipt.show();
|
||||||
|
LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "computer");
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("ignite1771", "computer");
|
||||||
|
receipt.show();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
public interface ApplicationServices {
|
||||||
|
|
||||||
|
ReceiptViewModel loggedInUserPurchase(String userName, String itemName);
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
public class ApplicationServicesImpl implements ApplicationServices {
|
||||||
|
|
||||||
|
private DomainServicesImpl domain = new DomainServicesImpl();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReceiptViewModel loggedInUserPurchase(String userName, String itemName) {
|
||||||
|
if (isDownForMaintenance()) {
|
||||||
|
return new DownForMaintenance();
|
||||||
|
}
|
||||||
|
return this.domain.purchase(userName, itemName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDownForMaintenance() {
|
||||||
|
return MaintenanceLock.getInstance().isLock();
|
||||||
|
}
|
||||||
|
}
|
150
special-case/src/main/java/com/iluwatar/specialcase/Db.java
Normal file
150
special-case/src/main/java/com/iluwatar/specialcase/Db.java
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class Db {
|
||||||
|
|
||||||
|
private static Db instance;
|
||||||
|
private Map<String, User> userName2User;
|
||||||
|
private Map<User, Account> user2Account;
|
||||||
|
private Map<String, Product> itemName2Product;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instance of Db.
|
||||||
|
*
|
||||||
|
* @return singleton instance of Db class
|
||||||
|
*/
|
||||||
|
public static Db getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (Db.class) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new Db();
|
||||||
|
instance.userName2User = new HashMap<>();
|
||||||
|
instance.user2Account = new HashMap<>();
|
||||||
|
instance.itemName2Product = new HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed a user into Db.
|
||||||
|
*
|
||||||
|
* @param userName of the user
|
||||||
|
* @param amount of the user's account
|
||||||
|
*/
|
||||||
|
public void seedUser(String userName, Double amount) {
|
||||||
|
User user = new User(userName);
|
||||||
|
instance.userName2User.put(userName, user);
|
||||||
|
Account account = new Account(amount);
|
||||||
|
instance.user2Account.put(user, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed an item into Db.
|
||||||
|
*
|
||||||
|
* @param itemName of the item
|
||||||
|
* @param price of the item
|
||||||
|
*/
|
||||||
|
public void seedItem(String itemName, Double price) {
|
||||||
|
Product item = new Product(price);
|
||||||
|
itemName2Product.put(itemName, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a user with the userName.
|
||||||
|
*
|
||||||
|
* @param userName of the user
|
||||||
|
* @return instance of User
|
||||||
|
*/
|
||||||
|
public User findUserByUserName(String userName) {
|
||||||
|
if (!userName2User.containsKey(userName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return userName2User.get(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an account of the user.
|
||||||
|
*
|
||||||
|
* @param user in Db
|
||||||
|
* @return instance of Account of the user
|
||||||
|
*/
|
||||||
|
public Account findAccountByUser(User user) {
|
||||||
|
if (!user2Account.containsKey(user)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return user2Account.get(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a product with the itemName.
|
||||||
|
*
|
||||||
|
* @param itemName of the item
|
||||||
|
* @return instance of Product
|
||||||
|
*/
|
||||||
|
public Product findProductByItemName(String itemName) {
|
||||||
|
if (!itemName2Product.containsKey(itemName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return itemName2Product.get(itemName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class User {
|
||||||
|
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
public User(String userName) {
|
||||||
|
this.userName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReceiptDto purchase(Product item) {
|
||||||
|
return new ReceiptDto(item.getPrice());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Account {
|
||||||
|
|
||||||
|
private Double amount;
|
||||||
|
|
||||||
|
public Account(Double amount) {
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Withdraw the price of the item from the account.
|
||||||
|
*
|
||||||
|
* @param price of the item
|
||||||
|
* @return instance of MoneyTransaction
|
||||||
|
*/
|
||||||
|
public MoneyTransaction withdraw(Double price) {
|
||||||
|
if (price > amount) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new MoneyTransaction(amount, price);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getAmount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Product {
|
||||||
|
|
||||||
|
private Double price;
|
||||||
|
|
||||||
|
public Product(Double price) {
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
public interface DomainServices {
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
public class DomainServicesImpl implements DomainServices {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain purchase with userName and itemName, with validation for userName.
|
||||||
|
*
|
||||||
|
* @param userName of the user
|
||||||
|
* @param itemName of the item
|
||||||
|
* @return instance of ReceiptViewModel
|
||||||
|
*/
|
||||||
|
public ReceiptViewModel purchase(String userName, String itemName) {
|
||||||
|
Db.User user = Db.getInstance().findUserByUserName(userName);
|
||||||
|
if (user == null) {
|
||||||
|
return new InvalidUser(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Db.Account account = Db.getInstance().findAccountByUser(user);
|
||||||
|
return purchase(user, account, itemName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain purchase with user, account and itemName,
|
||||||
|
* with validation for whether product is out of stock
|
||||||
|
* and whether user has insufficient funds in the account.
|
||||||
|
*
|
||||||
|
* @param user in Db
|
||||||
|
* @param account in Db
|
||||||
|
* @param itemName of the item
|
||||||
|
* @return instance of ReceiptViewModel
|
||||||
|
*/
|
||||||
|
private ReceiptViewModel purchase(Db.User user, Db.Account account, String itemName) {
|
||||||
|
Db.Product item = Db.getInstance().findProductByItemName(itemName);
|
||||||
|
if (item == null) {
|
||||||
|
return new OutOfStock(user.getUserName(), itemName);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReceiptDto receipt = user.purchase(item);
|
||||||
|
MoneyTransaction transaction = account.withdraw(receipt.getPrice());
|
||||||
|
if (transaction == null) {
|
||||||
|
return new InsufficientFunds(user.getUserName(), account.getAmount(), itemName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return receipt;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class DownForMaintenance implements ReceiptViewModel {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(DownForMaintenance.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
LOGGER.info("Down for maintenance");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class InsufficientFunds implements ReceiptViewModel {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(InsufficientFunds.class);
|
||||||
|
|
||||||
|
private String userName;
|
||||||
|
private Double amount;
|
||||||
|
private String itemName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor of InsufficientFunds.
|
||||||
|
*
|
||||||
|
* @param userName of the user
|
||||||
|
* @param amount of the user's account
|
||||||
|
* @param itemName of the item
|
||||||
|
*/
|
||||||
|
public InsufficientFunds(String userName, Double amount, String itemName) {
|
||||||
|
this.userName = userName;
|
||||||
|
this.amount = amount;
|
||||||
|
this.itemName = itemName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
LOGGER.info("Insufficient funds: " + amount + " of user: " + userName
|
||||||
|
+ " for buying item: " + itemName);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class InvalidUser implements ReceiptViewModel {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(InvalidUser.class);
|
||||||
|
|
||||||
|
private final String userName;
|
||||||
|
|
||||||
|
public InvalidUser(String userName) {
|
||||||
|
this.userName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
LOGGER.info("Invalid user: " + userName);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class MaintenanceLock {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(MaintenanceLock.class);
|
||||||
|
|
||||||
|
private static MaintenanceLock instance;
|
||||||
|
private boolean lock = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instance of MaintenanceLock.
|
||||||
|
*
|
||||||
|
* @return singleton instance of MaintenanceLock
|
||||||
|
*/
|
||||||
|
public static MaintenanceLock getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (MaintenanceLock.class) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new MaintenanceLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLock() {
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLock(boolean lock) {
|
||||||
|
this.lock = lock;
|
||||||
|
LOGGER.info("Maintenance lock is set to: " + lock);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
public class MoneyTransaction {
|
||||||
|
|
||||||
|
private Double amount;
|
||||||
|
private Double price;
|
||||||
|
|
||||||
|
public MoneyTransaction(Double amount, Double price) {
|
||||||
|
this.amount = amount;
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class OutOfStock implements ReceiptViewModel {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(OutOfStock.class);
|
||||||
|
|
||||||
|
private String userName;
|
||||||
|
private String itemName;
|
||||||
|
|
||||||
|
public OutOfStock(String userName, String itemName) {
|
||||||
|
this.userName = userName;
|
||||||
|
this.itemName = itemName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
LOGGER.info("Out of stock: " + itemName + " for user = " + userName + " to buy");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class ReceiptDto implements ReceiptViewModel {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ReceiptDto.class);
|
||||||
|
|
||||||
|
private Double price;
|
||||||
|
|
||||||
|
public ReceiptDto(Double price) {
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
LOGGER.info("Receipt: " + price + " paid");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
public interface ReceiptViewModel {
|
||||||
|
|
||||||
|
void show();
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application test.
|
||||||
|
*/
|
||||||
|
public class AppTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExecuteWithoutException() {
|
||||||
|
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
package com.iluwatar.specialcase;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import ch.qos.logback.classic.Logger;
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import ch.qos.logback.core.read.ListAppender;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special cases unit tests. (including the successful scenario {@link ReceiptDto})
|
||||||
|
*/
|
||||||
|
public class SpecialCasesTest {
|
||||||
|
private static ApplicationServices applicationServices;
|
||||||
|
private static ReceiptViewModel receipt;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
Db.getInstance().seedUser("ignite1771", 1000.0);
|
||||||
|
Db.getInstance().seedItem("computer", 800.0);
|
||||||
|
Db.getInstance().seedItem("car", 20000.0);
|
||||||
|
|
||||||
|
applicationServices = new ApplicationServicesImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void beforeEach() {
|
||||||
|
MaintenanceLock.getInstance().setLock(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDownForMaintenance() {
|
||||||
|
final Logger LOGGER = (Logger) LoggerFactory.getLogger(DownForMaintenance.class);
|
||||||
|
|
||||||
|
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||||
|
listAppender.start();
|
||||||
|
LOGGER.addAppender(listAppender);
|
||||||
|
|
||||||
|
MaintenanceLock.getInstance().setLock(true);
|
||||||
|
receipt = applicationServices.loggedInUserPurchase(null, null);
|
||||||
|
receipt.show();
|
||||||
|
|
||||||
|
List<ILoggingEvent> loggingEventList = listAppender.list;
|
||||||
|
assertEquals("Down for maintenance", loggingEventList.get(0).getMessage());
|
||||||
|
assertEquals(Level.INFO, loggingEventList.get(0).getLevel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidUser() {
|
||||||
|
final Logger LOGGER = (Logger) LoggerFactory.getLogger(InvalidUser.class);
|
||||||
|
|
||||||
|
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||||
|
listAppender.start();
|
||||||
|
LOGGER.addAppender(listAppender);
|
||||||
|
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("a", null);
|
||||||
|
receipt.show();
|
||||||
|
|
||||||
|
List<ILoggingEvent> loggingEventList = listAppender.list;
|
||||||
|
assertEquals("Invalid user: a", loggingEventList.get(0).getMessage());
|
||||||
|
assertEquals(Level.INFO, loggingEventList.get(0).getLevel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOutOfStock() {
|
||||||
|
final Logger LOGGER = (Logger) LoggerFactory.getLogger(OutOfStock.class);
|
||||||
|
|
||||||
|
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||||
|
listAppender.start();
|
||||||
|
LOGGER.addAppender(listAppender);
|
||||||
|
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("ignite1771", "tv");
|
||||||
|
receipt.show();
|
||||||
|
|
||||||
|
List<ILoggingEvent> loggingEventList = listAppender.list;
|
||||||
|
assertEquals("Out of stock: tv for user = ignite1771 to buy"
|
||||||
|
, loggingEventList.get(0).getMessage());
|
||||||
|
assertEquals(Level.INFO, loggingEventList.get(0).getLevel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsufficientFunds() {
|
||||||
|
final Logger LOGGER = (Logger) LoggerFactory.getLogger(InsufficientFunds.class);
|
||||||
|
|
||||||
|
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||||
|
listAppender.start();
|
||||||
|
LOGGER.addAppender(listAppender);
|
||||||
|
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("ignite1771", "car");
|
||||||
|
receipt.show();
|
||||||
|
|
||||||
|
List<ILoggingEvent> loggingEventList = listAppender.list;
|
||||||
|
assertEquals("Insufficient funds: 1000.0 of user: ignite1771 for buying item: car"
|
||||||
|
, loggingEventList.get(0).getMessage());
|
||||||
|
assertEquals(Level.INFO, loggingEventList.get(0).getLevel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReceiptDto() {
|
||||||
|
final Logger LOGGER = (Logger) LoggerFactory.getLogger(ReceiptDto.class);
|
||||||
|
|
||||||
|
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||||
|
listAppender.start();
|
||||||
|
LOGGER.addAppender(listAppender);
|
||||||
|
|
||||||
|
receipt = applicationServices.loggedInUserPurchase("ignite1771", "computer");
|
||||||
|
receipt.show();
|
||||||
|
|
||||||
|
List<ILoggingEvent> loggingEventList = listAppender.list;
|
||||||
|
assertEquals("Receipt: 800.0 paid"
|
||||||
|
, loggingEventList.get(0).getMessage());
|
||||||
|
assertEquals(Level.INFO, loggingEventList.get(0).getLevel());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user