Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get spock to execute a different method at runtime using an Annotation Extension?

First, in case there is a simpler way to solve this problem, here is an outline of what I am trying to accomplish. I want to Annotate my test methods with a KnownIssue annotation (extending AbstractAnnotationDrivenExtension) that takes a defect ID as a parameter and checks the status of the defect before executing the tests. If the defect is fixed, it will continue execution, if it is not fixed I want it to ignore the test, but if it is closed or deleted, I want to induce a test failure with logging stating that the test should be removed or updated and the annotation removed since the defect is now closed or deleted.

I have everything working up until inducing a test failure. What I have tried that doesn't work:

  • Throwing an exception in the visitFeatureAnnotation method, which causes a failure which causes all tests thereafter not to execute.
  • Creating a class that extends Spec and including a test method that logs a message and fails, then tried to use feature.featureMethod.setReflection() to set the method to execute to the other method. In this case, I get a java.lang.IllegalArgumentException : object is not an instance of declaring class
  • I then tried using ExpandoMetaClass to add a method directly to the declaringClass, and point feature.featureMethod.setReflection to point to it, but I still get the same IllegalArgumentException.

Here is what I have inside of my visitFeatureAnnotation method for my latest attempt:

def myMetaClass = feature.getFeatureMethod().getReflection().declaringClass.metaClass
myMetaClass.KnownIssueMethod = { -> return false }
feature.featureMethod.setReflection(myMetaClass.methods[0].getDoCall().getCachedMethod());

Any other ideas on how I could accomplish this, and either induce a test failure, or replace the method with another that will fail?

like image 873
Sam Woods Avatar asked Mar 27 '15 23:03

Sam Woods


1 Answers

Ok... I finally came up with a solution. Here is what I got working. Within the visitFeatureAnnotation method I add a CauseFailureInterceptor that I created.

Here is the full source in case anyone is interested, just requires you to extend the KnownIssueExtension and implement the abstract method getDefectStatus:

public abstract class KnownIssueExtension extends AbstractAnnotationDrivenExtension<KnownIssue> {
    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(KnownIssueExtension.class)
    public void visitFeatureAnnotation(KnownIssue knownIssue, FeatureInfo feature) {
        DefectStatus status = null
        try{
            status = getDefectStatus(knownIssue.value())
        } catch(Exception ex){
            LOGGER.warn("Unable to determine defect status for defect ID '{}', test case {}", knownIssue.value(), feature.getName())
            // If we can't get info from Defect repository, just skip it, it should not cause failures or cause us not to execute tests.
        }
        if (status != null){
            if(!status.open && !status.fixed){
                LOGGER.error("Defect with ID '{}' and title '{}' is no longer in an open status and is not fixed, for test case '{}'.  Update or remove test case.", knownIssue.value(), status.defectTitle, feature.getName())
                feature.addInterceptor(new CauseFailureInterceptor("Defect with ID '" + knownIssue.value() + "' and title '" + status.defectTitle + "' is no longer in an open status and is not fixed, for test case '" + feature.getName() + "'.  Update or remove test case."))
            }else if (status.open && !status.fixed){
                LOGGER.warn("Defect with ID '{}' and title '{}' is still open and has not been fixed.  Not executing test '{}'", knownIssue.value(), status.defectTitle, feature.getName())
                feature.setSkipped(true)
            }else if (!status.open && status.fixed){
                LOGGER.error("Defect with ID '{}' and title '{}' has been fixed and closed.  Remove KnownIssue annotation from test '{}'.", knownIssue.value(), status.defectTitle, feature.getName())
                feature.addInterceptor(new CauseFailureInterceptor("Defect with ID '" + knownIssue.value() + "' and title '" + status.defectTitle + "' has been fixed and closed.  Remove KnownIssue annotation from test '" + feature.getName() + "'."))
            }else { // status.open && status.fixed
                LOGGER.warn("Defect with ID '{}' and title '{}' has recently been fixed.  Remove KnownIssue annotation from test '{}'", knownIssue.value(), status.defectTitle, feature.getName())
            }
        }
    }

    public abstract DefectStatus getDefectStatus(String defectId)

}

public class CauseFailureInterceptor extends AbstractMethodInterceptor{
    public String failureReason
    public CauseFailureInterceptor(String failureReason = ""){
        this.failureReason = failureReason
    }

    @Override
    public void interceptFeatureExecution(IMethodInvocation invocation) throws Throwable {
        throw new Exception(failureReason)
    }
}

class DefectStatus{
    boolean open
    boolean fixed
    String defectTitle
}
like image 127
Sam Woods Avatar answered Nov 11 '22 19:11

Sam Woods