All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.lpandzic.junit.bdd.Bdd Maven / Gradle / Ivy

Go to download

Provides a simple and fluent API for structuring test code within when and then blocks used in Behavior-driven development

There is a newer version: 2.1
Show newest version
package com.github.lpandzic.junit.bdd;

import java.util.Optional;

/**
 * Bdd provides a simple and fluent API for structuring test code within when and then blocks used in Behavior-driven
 * development.
 *
 * 

Following static import is useful for simpler syntax when using JUnit-BDD: *

 * import static com.github.lpandzic.junit.bdd.Bdd.when;
 * 
* *

Return value assertion

* For a given class {@code DeathStar} that contains method with signature {@code Target fireAt(Target target) throws * TargetAlreadyDestroyedException} where {@code TargetAlreadyDestroyedException} is a checked exception, * we can do the following value assertion: *
{@code
 * when(deathStar.fireAt(alderaan)).then(target -> {
 *     assertThat(target.isDestroyed(), is(true));
 *     assertThat(target, is(alderaan));
 *     assertThat(target, is(not(coruscant)));
 * });
 * }
* *

Thrown exception assertion

* In order to catch exception for an assertion we pass a lambda to the when block: *
{@code
 * when(deathStar.fireAt(alderaan));
 * when(() -> deathStar.fireAt(alderaan)).then(thrownException -> {
 *     assertThat(thrownException, is(instanceOf(TargetAlreadyDestroyedException.class)));
 *     assertThat(thrownException.getMessage(), is(equalTo("Cannot fire at a destroyed " + alderaan)));
 * });
 * }
* *

Thrown checked exceptions assertion

* If we decide to change the {@code fireAt} method so that it doesn't throw the {@code * TargetAlreadyDestroyedException} the test mentioned in previous sub chapter will fail, * but it will still compile. Since {@code TargetAlreadyDestroyedException} is a checked exception we can use * Generics to prevent that test from compiling and reduce the time required to detect the error! To use this feature * change {@code then} to {@code thenChecked} and use {@code isA} matcher: *
{@code
 * when(deathStar.fireAt(alderaan));
 * when(() -> deathStar.fireAt(alderaan)).thenChecked(thrownException -> {
 *     assertThat(thrownException, isA(TargetAlreadyDestroyedException.class));
 *     assertThat(thrownException.getMessage(), is(equalTo("Cannot fire at a destroyed " + alderaan)));
 * });
 * }
*

Now if we decide to change the signature of fireAt not to include TargetAlreadyDestroyedException we get a * compilation error.

* *

Assertion framework flexibility

* Although Hamcrest was used in previous examples you are free to use any Java assertion framework. For example, * the first testing example can be translated to: *
    *
  • plain JUnit assertions *
      *
    1. Return value assertion *
      {@code
       * when(deathStar.fireAt(alderaan)).then(target -> {
       *     assertTrue(target.isDestroyed());
       *     assertEquals(target, alderaan);
       *     assertNotEquals(target, coruscant);
       * });
       * }
    2. *
    3. Thrown exception assertion *
      {@code
       * when(deathStar.fireAt(alderaan));
       * when(() -> deathStar.fireAt(alderaan)).then(thrownException -> {
       *     assertEquals(TargetAlreadyDestroyedException.class, thrownException.getClass());
       *     assertEquals("Cannot fire at a destroyed " + alderaan, thrownException.getMessage());
       * });
       * }
    4. *
    *
  • AssertJ *
      *
    1. Return value assertion *
      {@code
       * when(deathStar.fireAt(alderaan)).then(target -> {
       *     assertThat(target.isDestroyed()).isTrue();
       *     assertThat(target).isEqualTo(alderaan);
       *     assertThat(target).isNotEqualTo(coruscant);
       * });
       * }
    2. *
    3. Thrown exception assertion *
      {@code
       * when(deathStar.fireAt(alderaan));
       * when(() -> deathStar.fireAt(alderaan)).then(thrownException -> {
       *     assertThat(thrownException).isExactlyInstanceOf(TargetAlreadyDestroyedException.class);
       *     assertThat(thrownException.getMessage()).isEqualTo("Cannot fire at a destroyed " + alderaan);
       * });
       * }
    4. *
    *
  • *
* * @author Lovro Pandzic * @see Introducing Bdd * @see GivenWhenThen article by M. Fowler */ public final class Bdd { /** * Used for specifying behavior that should throw an throwable. * *

Note: Not defining then inside the test after calling this method will cause throwable to be * silently swallowed and can cause subsequent test to fail on {@link #requireThatNoUnexpectedExceptionWasThrown()}. * }

* * @param throwableSupplier supplier or throwable * @param the type of * * @return new {@link Then.Throws} */ public static Then.Throws when(ThrowableSupplier throwableSupplier) { requireThatNoUnexpectedExceptionWasThrown(); return new When().when(throwableSupplier); } /** * Used for specifying behavior that should return a value. * * @param value returned by the specified behavior * @param type of {@code value} * * @return new {@link Then.Returns} */ public static Then.Returns when(T value) { requireThatNoUnexpectedExceptionWasThrown(); return new When().when(value); } /** * {@link ThreadLocal} exception thrown or {@link Optional#empty()}. */ private static ThreadLocal> thrownException = new ThreadLocal<>().withInitial(Optional::empty); /** * Inserts the {@code throwable} into {@link #thrownException}. * * @param throwable to add */ static void putThrownException(Throwable throwable) { requireThatNoUnexpectedExceptionWasThrown(); thrownException.set(Optional.of(throwable)); } /** * Retrieves and removes {@link #thrownException}. * * Used by {@link Then} for consuming {@link #thrownException} * * @return {@link #thrownException} */ static Optional takeThrownException() { Optional thrownException = Bdd.thrownException.get(); Bdd.thrownException.set(Optional.empty()); return thrownException; } @SuppressWarnings("unchecked") static void throwUnexpectedException(Optional throwable) throws T { if (throwable.isPresent()) { throw (T) throwable.get(); } } /** * Throws {@link #thrownException} if present. * * @throws IllegalStateException if a {@code thrownException} already contains an exception, * the previous thrown exception is wrapped */ static void requireThatNoUnexpectedExceptionWasThrown() { if (thrownException.get().isPresent()) { throwUnexpectedException(takeThrownException()); } } private Bdd() { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy