JUnit ClassRule executes code before Spring Boot application shut down - java

I am running a Spring Boot integration test with org.springframework.boot.test.context.SpringBootTest.
I have written a JUnit 4 org.junit.rules.TestRule and I am using it as a org.junit.ClassRule.
It looks like this:
public class MyRule implements TestRule {
public Statement apply(final Statement statement, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
// do something
try {
statement.evaluate();
} finally {
// clean up
}
}
};
}
}
Unfortunately, the // clean up code is executed before the Spring Boot context is shut down.
Where do I have to put my code to execute it after the Spring application is shut down?

It seems that the code of all ClassRules is executed first and the Spring context is torn down right before the JVM terminates (see also https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ConfigurableApplicationContext.html#registerShutdownHook--).

When it comes to the tests of spring, I recommend using the Spring test execution listener.
Rules are JUnit's specific feature, and since the spring is integrated with JUnit as a Runner (#RunWith(SpringRunner.class)) they might interfere.
So, back to those test execution Listeners. Basically its a listener managed by Spring itself and it provides hooks to the test life-cycle where you can clean up your code
here is an example:
public class MySampleListener implements TestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
System.out.println("beforeTestClass");
}
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
System.out.println("prepareTestInstance");
}
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
System.out.println("beforeTestMethod");
}
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
System.out.println("afterTestMethod");
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
System.out.println("afterTestClass");
}
}
If you don't need all the methods, you can use org.springframework.test.context.support.AbstractTestExecutionListener that provides an empty implementation for all these methods.
Now, in the test you supply an annotation #TestExecutionListeners:
#RunWith(SpringRunner.class)
... // stuff like #ContextConfiguration
#TestExecutionListeners(value = {MySampleListener.class, ...})
public class MySampleTest {
#Test
public void testFoo() {
//test me
}
}
If you want to merge the other listeners that spring runs by default, you can use this annotation as follows:
#TestExecutionListeners(value = MySampleListener.class,
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)

Related

testing camel quartz route

I try to make a junit test for apache camel route.
Something like this :
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(
loader = CamelSpringDelegatingTestContextLoader.class
)
public class MyExportRouteBuilderIT extends CamelTestSupport {
#Test
public void test() {
// trigger and check the files made by route builder processor
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new MyExportRouteBuilder();
}
}
The builder class is defined like this
from("quartz2://exportJob?cron=" + cronTrigger)
.setHeader(FILE_NAME, expression(FILE_NAME_FORMAT))
.process(myExportRouteProcessor)
.marshal(new BindyCsvDataFormat(MyExportData.class))
.to("file:///destination);
The 'myExportRouteProcessor' class just gets some data from the JPA repository and puts the results to the route.
What I want is to trigger this route in the test class to check if the whole process was properly finished.
Currently, processor is not fired. What should I do more ?
You can replace quartz2 component in your test with direct using AdviceWithRouteBuilder#replaceFromWith.
#Test
public void test() throws Exception{
//mock input route (replace quartz with direct)
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith("direct:triggerQuartz");
}
});
//trigger endpoint
sendBody("direct:triggerQuartz", null);
//do some assertions
}

Is there anything like Spring TestExecutionListener for TestSuite?

Currently for tests I'm using TestExecutionListener and it works just perfect
public class ExecutionManager extends AbstractTestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
System.out.println("beforeClass");
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
System.out.println("afterClass");
}
}
Test classes:
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners(ExecutionManager.class)
public final class TC_001 {
#Test
public void test() {
System.out.println("Test_001");
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners(ExecutionManager.class)
public final class TC_002 {
#Test
public void test() {
System.out.println("Test_002");
}
}
When I include those tests in test suite, beforeTestClass(TestContext testContext) and afterTestClass(TestContext testContext) methods are executed for each test class, what is quite logical:
#RunWith(Suite.class)
#Suite.SuiteClasses({
TC_001.class,
TC_002.class
})
public final class TS_001 {
}
Is there anything like SuiteExecutionListener (TestExecutionListener for suites)?
Basically I need non-static #BeforeClass and #AfterClass for suite
OR
In ExecutionListener I need to find out what class has been launched: case or suite. For this purpose I can:
analyze StackTrace and get calling class
use Reflection.getCallerClass(int i) (which is deprecated)
pass caller class to ExecutionManager (By the way, how can I do that? Is it possible to put Object into TestContext like in Android Bundle?)
But I don't really like those solutions. SuiteExecutionListener is much more preferable
Thank you
No, there is unfortunately no such thing as a SuiteExecutionListener in the Spring TestContext Framework (TCF).
The TCF does not integrate with JUnit 4 at the suite level.
If you want to store something in the TestContext, that's not a problem. TestContext implements org.springframework.core.AttributeAccessor, so you can store attributes in the TestContext. Note, however, that the lifecycle of a given TestContext is tied to a test class.

Why does cucumber run #Before in all glue code files

I have a problem with my cucumber tests. It runs the #Before method in
all the glue classes.
For example. This feature file have one glue code in the MainStepDef.class.
#language: en
#run
Feature: Testing a feature
Test before method
Background:
Given stuff is created
Scenario: The test
When i do stuff
Then stuff will be done
The MainStepDef:
public class MainStepDef {
#Before
public void setup() {
System.out.println("This is OK!");
}
#Given("^stuff is created$")
public void stuff_is_created() throws Throwable {
}
#When("^i do stuff$")
public void i_do_stuff() throws Throwable {
}
#Then("^stuff will be done$")
public void stuff_will_be_done() throws Throwable {
}
}
I have an additional glue file called: OtherStep.class
public class OtherStepDef {
#Before
public void setup() {
throw new RuntimeException("No! Bad code! BAD CODE!");
}
#Given("^some other stuff is also created$")
public void some_other_stuff_is_also_created() throws Throwable {
}
}
And finally I have my runner class.
#RunWith(Cucumber.class)
#CucumberOptions(strict = true, tags = {"#run", "~#ignore" },
format = {"html:target/systemtest", "pretty" }, features = "classpath:features/",
glue = {"com.sorkmos.stepdef" }, monochrome = true)
public class RunFeatures {
}
When I run this I get the runtime exception from the OtherStepDef setup method.
Why does this happen? Should it not execute only the glue needed for the feature?
Example project:
https://github.com/cannibalcow/cucumberproblem
This is the intended behaviour of Cucumber: https://groups.google.com/forum/#!topic/cukes/7gILvMsE2Js
This is the intended behaviour of the #Before and #After hooks: they
are not related to each step definition. Every hook is run on each
scenario (unless it's filtered out by tags).

Arquillian: combining local and container code

I'm working with Arquillian with the JBoss 7 managed container. I'm writing a test to do the following:
Prepare the test locally, not on the JBoss server.
Run the test on the JBoss server.
Validate the output, not on the JBoss server.
Here is my first attempt at this:
#RunWith(Arquillian.class)
public class NotWorking {
#Inject
private Service service;
#Deployment
public static Archive<?> createDeployment() {
// ...
}
#Test
public void testService() throws Exception {
prepare();
service.executeService();
validate();
}
#RunAsClient
public void prepare() throws Exception {
LocalOnlyClass.prepare();
}
#RunAsClient
public void validate() throws Exception {
LocalOnlyClass.validate();
}
}
Unfortunately this doesn't work. Arquillian tries to run the preparation and validation on the server and fails to find the LocalOnlyClass. I can get this to work as follows but its ugly:
#RunWith(Arquillian.class)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class Working {
#Inject
private Service service;
#Deployment
public static Archive<?> createDeployment() {
// ...
}
#Test
#RunAsClient
public void testService1Prepare() throws Exception {
LocalOnlyClass.prepare();
}
#Test
public void testService2Test() throws Exception {
service.executeService();
}
#Test
#RunAsClient
public void testService3Validate() throws Exception {
LocalOnlyClass.validate();
}
}
Does anyone know of a better way to do this that avoids the "fake" tests?
I am not so sure about your comment:
Unfortunately this doesn't work. Arquillian tries to run the
preparation and validation on the server and fails to find the
LocalOnlyClass.
If your problem really lies in where the code gets executed, I don't see how JUnits #FixMethodOrder would make a difference. It just forces a execution order of the tests. The same can be achieved with Arquillian's #InSequence annotation which is what I would recommend in your case.

Stopping JUnit suite if particular test fails

I have a JUnit test suite in the form:
#RunWith(Suite.class)
#Suite.SuiteClasses( { xx.class, yy.cass })
public class AllTests {
public static Test suite() {
TestSuite suite = new TestSuite(AllTests.class.getName());
//$JUnit-BEGIN$
//$JUnit-END$
return suite;
}
}
This then calls vanilla tests like this:
public class xxx {
#Test
public void test () throws {
...
I have a situation where I'd like to stop the rest of the test suite running if there's an error or fail in the first test. But errors / fails in the others are ok and the suite should complete as many other tests as it can. Basically the first test failing would indicate it isn't safe to run the rest.
Is this possible?
First you need junit RunListener:
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;
public class FailureListener extends RunListener {
private RunNotifier runNotifier;
public FailureListener(RunNotifier runNotifier) {
super();
this.runNotifier=runNotifier;
}
#Override
public void testFailure(Failure failure) throws Exception {
super.testFailure(failure);
this.runNotifier.pleaseStop();
}
}
Then prepare a suite:
public class StopOnFailureSuite extends Suite {
public StopOnFailureSuite(Class<?> klass, Class<?>[] suiteClasses) throws InitializationError {
super(klass, suiteClasses);
}
public StopOnFailureSuite(Class<?> klass) throws InitializationError {
super(klass, klass.getAnnotation(SuiteClasses.class).value());
}
#Override
public void run(RunNotifier runNotifier) {
runNotifier.addListener(new FailureListener(runNotifier));
super.run(runNotifier);
}
}
And run your suite:
#RunWith(StopOnFailureSuite.class)
#Suite.SuiteClasses({
FirstTestClass.class,
SecondTestClass.class,
...
})
What's wrong with calling System.exit()?
If it's first test then consider moving its validation to #BeforeClass and throw exception if it fails. Then only #AfterClass method would run in case of this exception.
Of course, that way you lack all the fixture artifacts created in test setup method(s).
Like your answer but using #Before in an integration test, I did something like this:
public class FooTest {
private static boolean bar;
#BeforeClass
public static void setUpBeforeClass() throws Exception {
bar = false;
}
#Before
public void setUp() throws Exception {
assertTrue(bar);
}
#Test
public void test() {
System.out.println("something");
assertTrue(true);
}
#Test
public void test1() {
System.out.println("Something2");
assertTrue(true);
}
}
Regards!
Based on the answer from Hiro2k (thanks!) I've used the following solution. It's a bit of a hack but it works.
The test which can prevent other tests running goes at the top of the #Suite.SuiteClasses list. That test then has the following:
private static boolean shouldStopRestOfSuite = false;
#Test
public void test () throws Throwable {
try {
... run some test code...
}
catch (Throwable e) {
shouldStopRestOfSuite = true;
throw e;
}
}
Note the above does need to catch Throwable (not exception) so it catches assertion errors. It also re-throws the error so it's logged by JUnit for analysis.
Then there's another test method:
#Test
public void testKillIfNeeded () throws Exception {
if (!shouldStopRestOfSuite) {
return;
}
System.out.println ("Test suite killed due to dangerous error / failure");
System.exit(1);
}
The above is run second and will kill the JUnit process.
Using this method the JUnit test won't end on fail / error if there's an issue but the fail / error is logged for analysis by JUnit and no further tests will run.
Not too pretty but it does the job :)
Firstly you should catch an error and check the same before you run the 2nd test.
#Rule
public ErrorCollector collector = new ErrorCollector();
1. Add Error.
collector.addError(new Throwable("first thing went wrong"));
2. Check before the dependent run.
collector.checkThat(getResult(), not(containsString("ERROR!")));
Reference - ErrorCollector
Are you running tests using ant?
You could write a custom test listener. You can set this in ant http://ant.apache.org/manual/Tasks/junit.html ( enableTestListenerEvents).
I find it troubling that this functionality is so tedious to implement in such a mature library. If you're using JUnit 5 / Jupiter you can use an extension called JUnit Pioneer (https://junit-pioneer.org).
With JUnit Pioneer you can simply add a #DisableIfTestFails annotation to your test class to make all tests stop when one fails.

Categories

Resources