Giter Site home page Giter Site logo

better-preconditions's Introduction

Better Preconditions Build Status

Inspired by Guava Preconditions and Jasmine, Better Preconditions is a set of Java APIs for creating fluent, readable and testable Java preconditions.

Maven Dependency

<dependency>
  <groupId>com.github.choonchernlim</groupId>
  <artifactId>better-preconditions</artifactId>
  <version>0.1.1</version>
</dependency>

Feature Highlights

  • Every precondition can be negated with not():-
expect(name, "Name").not().toBeBlank().check();
  • Labels are optional (although they would provide more helpful error messages):-
// this works
expect(endDate, "End Date").toBeAfter(startDate, "Start Date").check();

// this works too
expect(endDate).toBeAfter(startDate).check();
  • Preconditions can be chained:-
// age cannot be null and must be between 50 and 100, but not 55
expect(age, "Age")
    .not().toBeNull()
    .toBeEqualOrGreaterThan(50)
    .toBeEqualOrLessThan(100)
    .not().toBeEqual(55)
    .check();
  • Each precondition throws a specific exception, which allows the preconditions to be easily unit tested:-
// if null, throw ObjectNullPreconditionException
// if less than 50, throw NumberNotEqualOrGreaterThanPreconditionException
// if more than 100, throw NumberNotEqualOrLessThanPreconditionException
expect(age, "Age")
    .not().toBeNull() 
    .toBeEqualOrGreaterThan(50, "Lower Age Limit") 
    .toBeEqualOrLessThan(100, "Upper Age Limit")
    .check();
  • Each thrown exception contains helpful error message for debugging purpose:-
// if null, error message is "Age [ null ] must not be null"
// if less than 50, error message is "Age [ X ] must be equal to or greater than Lower Age Limit [ 50 ]"
// if more than 100, error message is "Age [ X ] must be equal to or less than Upper Age Limit [ 100 ]"
expect(age, "Age")
    .not().toBeNull() 
    .toBeEqualOrGreaterThan(50, "Lower Age Limit") 
    .toBeEqualOrLessThan(100, "Upper Age Limit")
    .check();
  • Highly extensible. You can provide your own custom matcher if needed:-
// name must not be blank and must start with "Cluster MEOW"
expect(name, "Name")
    .not().toBeBlank()
    .customMatcher(new Matcher<String>() {
        @Override
        public boolean match(String value, String label) {
            return name.startsWith("Cluster MEOW");
        }

        @Override
        public PreconditionException getException(String value, String label) {
            // if customMatcher(..) fails, throw this exception
            return new PreconditionException("Name must start be 'Cluster Meow'");
        }

        @Override
        public PreconditionException getNegatedException(String value, String label) {
            // if not().customMatcher(..) fails, throw this exception
            return new PreconditionException("Name must not start be 'Cluster Meow'");
        }
    })
    .check();

Real World Example

Let's compare between Guava Preconditions and Better Preconditions.

Guava Preconditions

public void compute(final String name, final Integer age, final LocalDate registrationDate) {
    // if blank, throw IllegalArgumentException
    checkArgument(!nullToEmpty(name).trim().isEmpty(), "Name cannot be blank");

    // if null, throw NullPointerException
    checkNotNull(age, "Age cannot be null");

    // if less than 50 or more than 100, throw IllegalArgumentException
    checkArgument(age >= 50 && age <= 100, "Age must be between 50 and 100");

    // if null, throw NullPointerException
    checkNotNull(registrationDate, "Registration date cannot be null");

    final LocalDate dateToday = LocalDate.now();

    // if earlier than today, throw IllegalArgumentException
    checkArgument(registrationDate.isEqual(dateToday) || registrationDate.isAfter(dateToday),
                  "Registration date must be equal to or after today");
}
  • Typing the error message for each precondition makes the code very messy. It gets even messier if we want to include the value(s) in the error message.
  • Typically, most preconditions would throw either IllegalArgumentException or IllegalArgumentException, which makes the API a little confusing to unit test.
  • If a method has a lot of preconditions, it becomes very unreadable.

Better Preconditions

public void compute(final String name, final Integer age, final LocalDate registrationDate) {
    // if blank, throw StringBlankPreconditionException
    expect(name, "Name")
            .not().toBeBlank()
            .check();

    // if null, throw ObjectNullPreconditionException
    // if less than 50, throw NumberNotEqualOrGreaterThanPreconditionException
    // if more than 100, throw NumberNotEqualOrLessThanPreconditionException
    expect(age, "Age")
            .not().toBeNull()
            .toBeEqualOrGreaterThan(50, "Lower Age Limit")
            .toBeEqualOrLessThan(100, "Upper Age Limit")
            .check();

    // if null, throw ObjectNullPreconditionException
    // if earlier than today, throw JodaTimeNotEqualOrAfterPreconditionException
    expect(registrationDate, "Registration Date")
            .not().toBeNull()
            .toBeEqualOrAfter(LocalDate.now(), "Today's Date")
            .check();
}
  • Short and succinct. Type only what's needed.
  • If one of the preconditions fails, a specific exception is thrown, which makes the API much easier to unit test.
  • Error message includes value(s), for example, if the age is less than 50, the following error message is produced: Age [ 39 ] must be equal to or greater than Lower Age Limit [ 50 ].
  • Method chaining makes the code very readable.

Better Preconditions API

Getting Started

  • Always begin with expect(var) and end with check().
  • Every precondition can be negated with not() prefix.
  • When chaining preconditions, the order of the preconditions is very important.

Object

These preconditions apply to all variable types.

  • toBeEqual(expectedValue)
  • toBeNull()
  • toBeSameType(expectedValue)

Boolean

  • toBeTrue()

Collection

  • toBeEmpty()

Joda Time

These preconditions can be used on all BaseLocal subclasses: LocalDate, LocalDateTime and LocalTime.

  • toBeAfter(expectedValue)
  • toBeBefore(expectedValue)
  • toBeEqualOrAfter(expectedValue)
  • toBeEqualOrBefore(expectedValue)

Number

These preconditions can be used on all Number subclasses: AtomicInteger, AtomicLong, BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short.

  • toBeEqualOrGreaterThan(expectedValue)
  • toBeEqualOrLessThan(expectedValue)
  • toBeGreaterThan(expectedValue)
  • toBeLessThan(expectedValue)

String

  • toBeBlank()

Helpful Links

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.