Apply '@Rule' after each '@Test' and before each '@After' in JUnit
Asked Answered
E

3

19

I have a test suite where I am logging out of the system in @After and closing the browser in @AfterClass. I am trying to use @Rule to take failed test screenshot using Selenium for every test method. I checked manually that @Rule only runs before every @Before but I want to set it up after @Test and before @After. I couldn't find out simple solution. Any help will be appreciated.

public class MorgatgeCalculatorTest  {

@Before
public void before(){
    System.out.println("I am before");
}
@BeforeClass
public static void beforeclass(){
    System.out.println("I am beforeclass");
}
@Test
    public void test(){
        System.out.println("I am Test");
    }
@Test
public void test2(){
    System.out.println("I am Test2");
}
@After
    public void after(){
        System.out.println("I am after");
    }
@AfterClass
        public static  void afterclass(){
            System.out.println("I am afterclass");

}
@Rule
ExpensiveExternalResource ExpensiveExternalResource = new ExpensiveExternalResource();

static class ExpensiveExternalResource implements MethodRule  {
    public ExpensiveExternalResource(){
        System.out.println("I am rule");
    }

    @Override
    public Statement apply(Statement arg0, FrameworkMethod arg1, Object arg2) {
        // TODO Auto-generated method stub
        return null;
    }    
}               

The output I am getting is

I am beforeclass
I am rule
I am before
I am Test
I am after
I am rule
I am before
I am Test2
I am after
I am afterclass
Eliseelisee answered 22/8, 2012 at 18:11 Comment(4)
I mentioned i only want to take screen shot only when a test fail. Not for every test :DEliseelisee
Funny. I was only interested in the order, so actually your question was my answer :)Jackboot
@GáborLipták :) I am glad!Eliseelisee
@GáborLipták actually the shown order is not correct, as it only shows when the rule is instantiated, not when it is applied. The correct test would not have a constuctor in the Rule at all, but call arg0.evaluate() logging before and after the call. This would show that the rule is run around @Before / @After like is also noted in the JavaDoc of JUnit.Selfreproach
Q
27

Because of the way that rules are set up, you can't have a rule that comes after @before or before @after. You can think of rules like shells that you put on the test method. The first shell to go on is @before/@after. Thereafter the @rules are applied.

A quick way to do what you want to do is to avoid @After altogether. A rule can be created so that it will take a screenshot if a method fails and then execute yours after the code. It isn't quite as pretty as @After, but it works. (also I implemented TestRule because MethodRule has been depreciated).

public class MortgageCalculatorTest  {
    @Before
    public void before(){
        System.out.println("I am before");
    }

    @BeforeClass
    public static void beforeclass(){
        System.out.println("I am beforeclass");
    }

    @Test
    public void test(){
        System.out.println("I am a Test");
    }

    @Test
    public void test2(){
        System.out.println("I am a Failed Test");
        fail();
    }

    @AfterClass
            public static  void afterclass(){
                System.out.println("I am afterclass");

    }

    @Rule
    public ExpensiveExternalResource ExpensiveExternalResource = new ExpensiveExternalResource();

    public static class ExpensiveExternalResource implements TestRule  {


      //  public ExpensiveExternalResource(){}


        public class ExpansiveExternalResourceStatement extends Statement{

            private Statement baseStatement;

            public ExpansiveExternalResourceStatement(Statement b){
                baseStatement = b;
            }

            @Override
            public void evaluate() throws Throwable {
                try{
                    baseStatement.evaluate();
                }catch(Error e){
                    System.out.println("I take a Screenshot");
                    throw e;   
                }finally{
                    after();
                }
            }

            //Put your after code in this method!
            public void after(){
                System.out.println("I am after");
            }
        }

        public Statement apply(Statement base, Description description) {
            return new ExpansiveExternalResourceStatement(base);

        }


    }
}

All the work of the rule is done in a statement. A org.junit.runners.model.Statement is a class that represents a bundle of code. So here the apply method receives the bundle of code that you are putting a shell around. Apply returns your statement that executes the bundle of code that you gave it and surrounds it with a try/catch statement to catch the method failures.

The output for this method is:

I am beforeclass
I am before
I am a Test
I am after
I am before
I am a Failed Test
I take a Screenshot
I am after
I am afterclass

Hope this helps!

Quit answered 24/8, 2012 at 1:31 Comment(2)
Troy, Thanks for your comment. Got hints from your solution.Eliseelisee
I would suggest to add @After method with output so it will be visible, that baseStatement.evaluate() actually causes @Before, then test method, then @After to run.Juvenescent
E
5
public class ScreenshotTestRule implements MethodRule {
    public Statement apply(final Statement statement, final FrameworkMethod frameworkMethod, final Object o) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                try {
                    statement.evaluate();

                } catch (Throwable t) {
                    captureScreenshot(frameworkMethod.getName());
                    throw t; // rethrow to allow the failure to be reported to JUnit                     
                } finally {
                    tearDown();
                }
            }

            public void tearDown() {
                //logout to the system;
            }


            public void captureScreenshot(String fileName) {
                try {
                    new File("target/surefire-reports/screenshot").mkdirs(); // Insure directory is there
                    FileOutputStream out = new FileOutputStream("target/surefire-reports/screenshot/screenshot-" + fileName + ".png");
                    out.write(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES));
                    out.close();
                } catch (Exception e) {
                    // No need to crash the tests if the screenshot fails
                }
            }
        };
    }
}
Eliseelisee answered 27/8, 2012 at 14:42 Comment(0)
H
1

What about using the ExternalResource rule ?
Looks like you it can give you enough flexibility to what you need.
And if this is not exactly what you need, take a look at the source code of external resource.
It's quite understandble how to implement a rule for example that will work only after the test invocation.

Hierolatry answered 22/8, 2012 at 18:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.