Skip to content

Commit

Permalink
Merge pull request #78 from ppi-ag/feature-api-refactoring
Browse files Browse the repository at this point in the history
Feature api refactoring Issue #45
  • Loading branch information
JanSchankin committed Aug 10, 2021
2 parents 60c8a8b + d2374e1 commit 8013cf7
Show file tree
Hide file tree
Showing 26 changed files with 1,003 additions and 765 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
* This program is made available under the terms of the MIT License.
*/

package de.ppi.deepsampler.core.internal;

import de.ppi.deepsampler.core.api.Quantity;
package de.ppi.deepsampler.core.api;

public class FixedQuantity implements Quantity {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2020 PPI AG (Hamburg, Germany)
* This program is made available under the terms of the MIT License.
*/

package de.ppi.deepsampler.core.api;

import de.ppi.deepsampler.core.model.Answer;
import de.ppi.deepsampler.core.model.SampleDefinition;

/**
* <p>
* Provides a fluent API for creating a {@link SampleDefinition} for methods that return a value (i.e. functions).
* </p>
*
* <p>
* With the FunctionalSampleBuilder you are able to define:
* <ul>
* <li>A concrete return value</li>
* <li>An abstract {@link Answer} that executes arbitrary code and returns a value</li>
* </ul>
* <p>
* The return value will be used (and evaluated) when the stubbed method will be invoked.
* </p>
*
* @param <T> type of the class you want stub
*/
public class FunctionalSampleBuilder<T> extends VoidSampleBuilder {

/**
* Create a {@link FunctionalSampleBuilder} with a sampler of the class you want to build a sample for, and the sampleDefinition
* you want to extend.
*
* @param sampler the sampler {@link Sampler}
* @param sampleDefinition {@link SampleDefinition}
*/
@SuppressWarnings("unused")
public FunctionalSampleBuilder(final T sampler, final SampleDefinition sampleDefinition) {
super(sampleDefinition);
}

/**
* Defines the value, that will be returned when the stubbed method is invoked. This value is called the "Sample".
*
* @param sampleReturnValue the return value that should be returned by the stubbed method.
*/
public void is(final T sampleReturnValue) {
getSampleDefinition().setAnswer(stubInvocation -> sampleReturnValue);
}

/**
* In most cases it will be sufficient to define a fixed Sample as a return value for a stubbed method, but sometimes it
* is necessary to execute some logic that would compute the return value or that would even change some additional state.
* This can be done by using an Answer like so:
*
* <code>
* Sample.of(sampler.echo(anyString())).answer(invocation -&gt; invocation.getParameters().get(0));
* </code>
* <p>
* In essence using Answers gives free control on what a stubbed method should do.
*
* @param answer supplier you want to get evaluated when the stubbed method got invoked
*/
@SuppressWarnings("unchecked")
public void answers(final Answer<? extends Throwable> answer) {
getSampleDefinition().setAnswer((Answer<Throwable>) answer);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2020 PPI AG (Hamburg, Germany)
* This program is made available under the terms of the MIT License.
*/

package de.ppi.deepsampler.core.api;

import de.ppi.deepsampler.core.error.NotASamplerException;
import de.ppi.deepsampler.core.internal.ProxyFactory;
import de.ppi.deepsampler.core.model.SampleDefinition;
import de.ppi.deepsampler.core.model.SampleRepository;

import java.util.Objects;

/**
* This is the starting point for the definition of Samples in test classes.
*
* A "Sample" is an exemplary return value or an exemplary behavior of a method that fulfills a prerequisite of a particular test case. When the tested methods are not able
* to reproduce the Sample by their own means (i.e. due to changes in the underlying database, or passing of time, etc.), the methods can be coerced to reproduce the Sample using
* this API.
*
* A method is called "sampled" if the method is coerced to return a Sample.
*
* Objects which contain sampled methods, are called "Sampler".
*
* DeepSampler defines Samples on classes rather then on objects. This enables DeepSampler to change the behavior of all instances of these classes without the need to instantiate
* the Sampler manually and to distribute the Sampler into the objects that will be tested. The distribution is done by a Dependency Injection Framework like Spring or Guice.
*
* Methods of the class {@link Object} are ignored. Otherwise strange effects might appear, e.g. if Object::finalize is
* called by the garbage collector.
*
* @author Jan Schankin, Rico Schrage
*/
public class PersistentSample {

private PersistentSample() {
// This class is meant to be used as a frontend for a static fluent API and should never be instantiated.
}


/**
* Defines a sampled method by calling the method inside of the parameter. The returned {@link FunctionalSampleBuilder} will then offer possibilities to define the Sample,
* or in other words, it offers possibilities to override the default behavior or the return value of a method.
*
* @param sampledMethodCall The method call that will be sampled.
* @param <T> The type of the return value and therefore the type of the Sample.
* @return A {@link FunctionalSampleBuilder} which can be used to define the concrete Sample. <b>Do not</b> keep references to this object, it is intended to be used as a
* fluent API only.
*/
public static <T> PersistentSampleBuilder<T> of(final T sampledMethodCall) {
SampleDefinition currentSampleDefinition = SampleRepository.getInstance().getCurrentSampleDefinition();
SampleDefinition lastSampleDefinition = SampleRepository.getInstance().getLastSampleDefinition();

if (currentSampleDefinition == lastSampleDefinition) {
throw new NotASamplerException("sampledMethodCall is not a Sampler. Did you prepare the Sampler using Sampler.prepare() or @PrepareSampler?");
}

currentSampleDefinition.setMarkedForPersistence(true);

SampleRepository.getInstance().setLastSampleDefinition(currentSampleDefinition);
return new PersistentSampleBuilder<>(sampledMethodCall, currentSampleDefinition);
}

/**
* Along with the subsequent method call it defines a Sample for which the framework should start to track
* how often this Sample is used in the component. This is necessary to be able to verify the invocation
* of a specific method.
*
* @param sampler the sampler for which you want to activate a method call
* @param <T> the type of the target Class/sampler
* @return the sampler itself
*/
public static <T> T forVerification(final T sampler) {
Objects.requireNonNull(sampler);

if (!ProxyFactory.isProxyClass(sampler.getClass())) {
throw new NotASamplerException(sampler.getClass());
}

SampleRepository.getInstance().setMarkNextVoidSamplerForPersistence(true);

return sampler;
}



/**
* This method will set the <code>sampleId</code> of the last defined sampleDefinition. Mostly you
* want to set the sampleId with the Method {@link PersistentSampleBuilder#hasId(String)}. But in case of
* void-returning methods, it is not possible to create a {@link FunctionalSampleBuilder}. As a consequence
* you will need to set the id with this method.
*
* @param id the id you want to set.
*/
public static void setIdToLastMethodCall(final String id) {
SampleRepository.getInstance().getCurrentSampleDefinition().setSampleId(id);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2020 PPI AG (Hamburg, Germany)
* This program is made available under the terms of the MIT License.
*/

package de.ppi.deepsampler.core.api;

import de.ppi.deepsampler.core.model.SampleDefinition;

/**
* <p>
* Provides a fluent API for creating a {@link SampleDefinition} for persistent Samples that are loaded from a file or any other DataSource.
* The persistence (i.e. recording and loading samples) is managed by PersistentSampleManager.
* </p>
*
* <p>
* With the sampleBuilder you are able to define a sampleId, that is used to identify a Sample in the persistence. By default
* DeepSampler creates a sampleId from the signature of the stubbed method. If this signature is changed, maybe because of a future
* refactoring, the sample cannot be loaded from the persistence anymore. Defining manual sampleIds can be used to avoid this situation.
*
* </p>
*
* @param <T> type of the class you want stub
*/
public class PersistentSampleBuilder<T> extends SampleBuilder {

/**
* Create a {@link PersistentSampleBuilder} with a sampler of the class you want to build a sample for, and the sampleDefinition
* you want to extend.
*
* @param sampler the sampler {@link Sampler}
* @param sampleDefinition {@link SampleDefinition}
*/
@SuppressWarnings("unused")
public PersistentSampleBuilder(final T sampler, final SampleDefinition sampleDefinition) {
super(sampleDefinition);
}

/**
* Set an id for the current SampleDefinition. The Id is used to find a Sample for a stubbed method. By default
* sampleIds are generated from the signature of the sampled method. Therefore a change of the signature would mean, that
* DeepSample isn't able anymore to find the Sample for the method. To prevent this situation, manual sampleIds can be used.
*
* @param sampleId the sampleId you want to set
* @return this
*/
public PersistentSampleBuilder<T> hasId(final String sampleId) {
getSampleDefinition().setSampleId(sampleId);
return this;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
import de.ppi.deepsampler.core.model.SampleDefinition;
import de.ppi.deepsampler.core.model.SampleRepository;

import java.util.Objects;

/**
* This is the starting point for the definition of Samples in test classes.
*
Expand Down Expand Up @@ -41,15 +39,15 @@ private Sample() {


/**
* Defines a sampled method by calling the method inside of the parameter. The returned {@link SampleBuilder} will then offer possibilities to define the Sample,
* Defines a sampled method by calling the method inside of the parameter. The returned {@link FunctionalSampleBuilder} will then offer possibilities to define the Sample,
* or in other words, it offers possibilities to override the default behavior or the return value of a method.
*
* @param sampledMethodCall The method call that will be sampled.
* @param <T> The type of the return value and therefore the type of the Sample.
* @return A {@link SampleBuilder} which can be used to define the concrete Sample. <b>Do not</b> keep references to this object, it is intended to be used as a
* @return A {@link FunctionalSampleBuilder} which can be used to define the concrete Sample. <b>Do not</b> keep references to this object, it is intended to be used as a
* fluent API only.
*/
public static <T> SampleBuilder<T> of(final T sampledMethodCall) {
public static <T> FunctionalSampleBuilder<T> of(final T sampledMethodCall) {
SampleDefinition currentSampleDefinition = SampleRepository.getInstance().getCurrentSampleDefinition();
SampleDefinition lastSampleDefinition = SampleRepository.getInstance().getLastSampleDefinition();

Expand All @@ -59,27 +57,9 @@ public static <T> SampleBuilder<T> of(final T sampledMethodCall) {

SampleRepository.getInstance().setLastSampleDefinition(currentSampleDefinition);

return new SampleBuilder<>(sampledMethodCall, currentSampleDefinition);
return new FunctionalSampleBuilder<>(sampledMethodCall, currentSampleDefinition);
}

/**
* Along with the subsequent method call it defines a Sample for which the framework should start to track
* how often this Sample is used in the component. This is necessary to be able to verify the invocation
* of a specific method.
*
* @param sampler the sampler for which you want to activate a method call
* @param <T> the type of the target Class/sampler
* @return the sampler itself
*/
public static <T> T forVerification(final T sampler) {
Objects.requireNonNull(sampler);

if (!ProxyFactory.isProxyClass(sampler.getClass())) {
throw new NotASamplerException(sampler.getClass());
}

return sampler;
}

/**
* Along with the subsequent method call you can assert that this method call has been called
Expand All @@ -95,18 +75,6 @@ public static <T> T verifyCallQuantity(final Class<T> cls, final Quantity quanti
return ProxyFactory.createProxy(cls, new VerifySampleHandler(quantity, cls));
}

/**
* This method will set the <code>sampleId</code> of the last defined sampleDefinition. Mostly you
* want to set the sampleId with the Method {@link SampleBuilder#hasId(String)}. But in case of
* void-returning methods, it is not possible to create a {@link SampleBuilder}. As a consequence
* you will need to set the id with this method.
*
* @param id the id you want to set.
*/
public static void setIdToLastMethodCall(final String id) {
SampleRepository.getInstance().getCurrentSampleDefinition().setSampleId(id);
}

/**
* Defines a stubbed void method by calling the method inside of a lambda. The returned {@link VoidSampleBuilder} will then offer possibilities to define the Sample,
* or in other words, it offers possibilities to override the default behavior or the stubbed method.
Expand All @@ -125,7 +93,7 @@ public static <E extends Exception> VoidSampleBuilder of(final VoidCall<E> sampl
try {
sampledMethodCall.call();
} catch (final Exception e) {
throw new NotASamplerException("The VoidCall did throw an Exception. Did you call an unstubbed method inside of the lamda, " +
throw new NotASamplerException("The VoidCall did throw an Exception. Did you call an unstubbed method inside of the lambda, " +
"instead of a method on a Sampler?", e);
}

Expand Down
Loading

0 comments on commit 8013cf7

Please sign in to comment.