/* * Copyright 2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package spock.lang; import groovy.lang.Closure; import org.junit.runner.RunWith; import org.spockframework.util.Beta; import org.spockframework.lang.Wildcard; import org.spockframework.util.ExceptionUtil; import org.spockframework.runtime.*; import org.spockframework.runtime.GroovyRuntimeUtil; import spock.mock.MockingApi; /** * Base class for Spock specifications. All specifications must inherit from * this class, either directly or indirectly. * * @author Peter Niederwieser */ @RunWith(Sputnik.class) @SuppressWarnings("UnusedDeclaration") public abstract class Specification extends MockingApi { /** * The wildcard symbol. Used in several places as a <em>don't care</em> value: * <ul> * <li>Mock interactions</li> * Example: <tt>1 * foo.bar(_)</tt> * <li>Data parameterizations</li> * Example: <tt>[foo, _] << loadDataFromDb()</tt> * </ul> */ public static final Object _ = Wildcard.INSTANCE; /** * Specifies that the preceding <tt>when</tt> block should throw an exception. * May only occur as the initializer expression of a typed variable declaration * in a <tt>then</tt> block; the expected exception type is inferred from the * variable type. * <p>This form of exception condition is typically used if the thrown * exception instance is used in subsequent conditions. * * <p>Example: * <pre> * when: * "".charAt(0) * * then: * IndexOutOfBoundsException e = thrown() * e.message.contains(...) * </pre> * * @return the thrown exception instance */ public <T extends Throwable> T thrown() { throw new InvalidSpecException( "Exception conditions are only allowed in 'then' blocks, and may not be nested inside other elements"); } /** * Specifies that the preceding <tt>when</tt> block should throw an exception * of the given type. May only occur in a <tt>then</tt> block. * <p>This form of exception condition is typically used if the thrown * exception instance is <em>not</em> used in subsequent conditions. * * <p>Example: * <pre> * when: * "".charAt(0) * * then: * thrown(IndexOutOfBoundsException) * * @param type the expected exception type * @param <T> the expected exception type * @return the thrown exception instance */ public <T extends Throwable> T thrown(Class<T> type) { throw new InvalidSpecException( "Exception conditions are only allowed in 'then' blocks, and may not be nested inside other elements"); } /** * Specifies that no exception of the given type should be * thrown, failing with a {@link UnallowedExceptionThrownError} otherwise. * * @param type the exception type that should not be thrown */ public void notThrown(Class<? extends Throwable> type) { Throwable thrown = getSpecificationContext().getThrownException(); if (thrown == null) return; if (type.isAssignableFrom(thrown.getClass())) { throw new UnallowedExceptionThrownError(type, thrown); } ExceptionUtil.sneakyThrow(thrown); } /** * Specifies that no exception should be thrown, failing with a * {@link UnallowedExceptionThrownError} otherwise. */ public void noExceptionThrown() { Throwable thrown = getSpecificationContext().getThrownException(); if (thrown == null) return; throw new UnallowedExceptionThrownError(null, thrown); } /** * Used in a then-block to access an expression's value at the time just * before the previous where-block was entered. * * @param expression an arbitrary expression, except that it may not * reference variables defined in the then-block * @param <T> the expression's type * @return the expression's value at the time the previous where-block was * entered */ public <T> T old(T expression) { throw new InvalidSpecException("old() can only be used in a 'then' block"); } /** * Sets the specified object as the implicit target of the top-level conditions and/or * interactions contained in the specified code block, thereby avoiding the need to repeat * the same expression multiple times. Implicit conditions are supported. (In other words, * the {@code assert} keyword may be omitted.) If the target is {@code null}, a * {@code SpockAssertionError} is thrown. * * <p>A {@code with} block can be used anywhere in a spec, including nested positions * and helper methods. * * <p>Condition example: * * <pre> * def fred = new Person(name: "Fred", age: 42) * def spaceship = new Spaceship(pilot: fred) * * expect: * with(spaceship.pilot) { * name == "Fred" // shorthand for: spaceship.pilot.name == "Fred" * age == 42 * } * </pre> * * <p> Interaction example: * * <pre> * def service = Mock(Service) // has start(), stop(), and doWork() methods * def app = new Application(service) // controls the lifecycle of the service * * when: * app.run() * * then: * with(service) { * 1 * start() // shorthand for: 1 * service.start() * 1 * doWork() * 1 * stop() * } * </pre> * * @param target an implicit target for conditions and/or interactions * @param closure a code block containing top-level conditions and/or interactions */ @Beta public void with(Object target, Closure<?> closure) { if (target == null) { throw new SpockAssertionError("Target of 'with' block must not be null"); } closure.setDelegate(target); // for conditions closure.setResolveStrategy(Closure.DELEGATE_FIRST); GroovyRuntimeUtil.invokeClosure(closure, target); } /** * Same as {@link #with(Object, groovy.lang.Closure)}, except that it also states that * the specified target has the specified type, throwing a {@code SpockAssertionError} * otherwise. As a side effect, this may give better code completion in IDEs. * * <p>Example: * * <pre> * def fred = new Employee(name: "Fred", age: 42, employer: "MarsTravelUnited") * def spaceship = new Spaceship(pilot: fred) * * expect: * with(spaceship.pilot, Employee) { * name == "Fred" // shorthand for: spaceship.pilot.name == "Fred" * age == 42 * employer == "MarsTravelUnited" * } * </pre> * * @param target an implicit target for conditions and/or interactions * @param type the expected type of the target * @param closure a code block containing top-level conditions and/or interactions */ @Beta public void with(Object target, Class<?> type, Closure closure) { if (target != null && !type.isInstance(target)) { throw new SpockAssertionError(String.format("Expected target of 'with' block to have type '%s', but got '%s'", type, target.getClass().getName())); } with(target, closure); } @Beta public void verifyAll(Closure closure){ GroovyRuntimeUtil.invokeClosure(closure); } }