How to create custom JUnit4 claims that do not appear in crash trace

I would like to add some custom statements to our code base that are properly hidden from the crash path. I know how to write a public static method that someone can statically import. I know how to reuse old statements or throw a new AssertionError .

What I cannot figure out how to do this is to keep the new user statements out of the crash trace. We are used to the first hit in the crash trace. We are NOT the approval code itself, but a test code called the approval.

I know that there is a filtertrace attribute that controls stack filtering, but I cannot find good documentation on what I need to do to add new statements to the filter.

An example of what I want to do:

 package testassertions; import static newassertions.MyAssertions.myAssertTrue; import org.junit.Test; public class ExampleTest { @Test public void myAssertTruePassing() { myAssertTrue(true); } @Test public void myAssertTrueFailing() { myAssertTrue(false); } } 

 package newassertions; import static org.junit.Assert.assertTrue; public class MyAssertions { public static void myAssertTrue(boolean b) { assertTrue(b); } } 

Tracking failures myAssertTrueFailing () shows:

 java.lang.AssertionError at newassertions.MyAssertions.myAssertTrue(MyAssertions.java:8) at testassertions.ExampleTest.myAssertTrueFailing(ExampleTest.java:12) 

I only need to show:

 java.lang.AssertionError at testassertions.ExampleTest.myAssertTrueFailing(ExampleTest.java:12) 
+7
java junit4 java-5 assertion
source share
4 answers

As mentioned in another question about clearing noise from stack traces , filtering classes from your IDE is probably the easiest solution. In fact, the stack traces you specified in your question are already filtered out.

If you really wanted to do this in code, you can add filtering to your custom approval class, as shown below:

 package newassertions; import static org.junit.Assert.assertTrue; import java.util.ArrayList; public class MyAssertions { public static void myAssertTrue(boolean b) { try { assertTrue(b); } catch (AssertionError e) { filterStackTrace(e); throw e; } } private static void filterStackTrace(AssertionError error) { StackTraceElement[] stackTrace = error.getStackTrace(); if (null != stackTrace) { ArrayList<StackTraceElement> filteredStackTrace = new ArrayList<StackTraceElement>(); for (StackTraceElement e : stackTrace) { if (!"newassertions.MyAssertions".equals(e.getClassName())) { filteredStackTrace.add(e); } } error.setStackTrace(filteredStackTrace.toArray(new StackTraceElement[0])); } } } 

In this example, the name of the deferred class "newassertions.MyAssertions" (hard-coded) is filtered from the stack trace. This mechanism, obviously, will also work to filter the stack trace from the AssertionError you created, and not just those that were raised from other statements.

+2
source share

Have you considered using org.junit.Assert.assertThat with Hamcrest matches?

With Hamcrest, you donโ€™t need to change the approval methods, but instead run your own interlocutors. For example, to verify that a password with the BCrypt hash extension matches a simple password, write the match as follows:

 public class MatchesPassword extends TypeSafeMatcher<String> { private static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder(); private final String password; public MatchesPassword(String password) { this.password = password; } @Override protected boolean matchesSafely(String encodedPassword) { return PASSWORD_ENCODER.matches(password, encodedPassword); } @Override public void describeTo(Description description) { description.appendText("matches password "); description.appendValue(password); } } 

Then add a method that you can statically import:

 public class CustomMatchers { public static Matcher<String> matchesPassword(String password) { return new MatchesPassword(password); } } 

Finally, write your test as follows:

 @Test public void passwordShouldMatch() { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder() String plainPassword = "secret"; String hashedPassword = passwordEncoder.encode(plainPassword); assertThat(hashedPassword, matchesPassword(plainPassword)); } 

The discrepancy will be logged in the console as follows:

 java.lang.AssertionError: Expected: matches password "wrong" but: was "$2a$10$5lOyLzUeKMAYPJ5A3y5KfOi747DocksLPHgR7GG3XD8pjp8mhaf0m" at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:18) at org.junit.Assert.assertThat(Assert.java:956) at org.junit.Assert.assertThat(Assert.java:923) ... 

Note. BCryptPasswordEncoder is in Spring Security and is just used as an example.

+4
source share

My go-with solution will also be an IDE filter, as others have already pointed out. If you execute a โ€œhard-codedโ€ solution, this will be less visible during the automatic build process.

In Eclipse, you can open the settings and select Java -> JUnit and add classes or packages using the buttons on the right.

But just for fun:


If you really want to do this programmatically, @gar's solution sounds pretty reasonable. However, if you have more statements, it can be a little tedious.

What you can also do is subclass AssertionError and the stacktrace filter in its root.

 public class MyAssertionError extends AssertionError { public MyAssertionError(String message) { super(message); } @Override public synchronized Throwable fillInStackTrace() { super.fillInStackTrace(); filterStackTrace(); return this; } protected void filterStackTrace() { StackTraceElement[] trace = getStackTrace(); ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>(trace.length); for (StackTraceElement element : trace) { if (!element.getClassName().equals("newassertions.MyAssertions")) { list.add(element); } } this.setStackTrace(list.toArray(new StackTraceElement[0])); } } 

Note two things here: 1) the class name StackTraceElement never be null, so its fine to write a constant on the right side 2) if you put all your statements in a separate package, you can also write element.getClassName().startsWith("newassertions")

Then your approval class will look like this:

 package newassertions; public class MyAssertions { public static void myAssertTrue(boolean b) { if (!b) { fail(null); } } public static void fail(String message) { if (message == null) { throw new MyAssertionError(message); } throw new MyAssertionError(message); } } 

That way you could not call methods from Assert , but if you write more complex statements, there are several reasons to do so anyway. However, this will keep your confirmation code a bit cleaner than wrapping everything in large try-catch blocks.

+2
source share

You can use the custom JUnit method rule along with custom statements. Custom statements can work with the AssertionError subtype. This would allow you to use Junit statements and user statements simultaneously.

Example

Here is an example that uses the custom class MyAssert , which throws a MyAssertionError if the statement fails. The JUnit rule handles MyAssertionError and hides any details of the failure trace.

 public class RuleTest { @Rule public TestVerifier testVerifier = new TestVerifier(); @Test public void myAssertOk() { MyAssert.assertCondition("ok", true); } @Test public void myAssertNotOk() { MyAssert.assertCondition("nok", false); } @Test public void junitAssertNotOk() { assertTrue(false); } @Test public void junitAssertOk() { assertTrue(true); } static class TestVerifier implements TestRule { @Override public Statement apply(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { try { base.evaluate(); } catch (MyAssertionError t) { throw new AssertionError("Test failed: " + description.getMethodName()); } } }; } } static class MyAssertionError extends AssertionError { public MyAssertionError(Object detailMessage) { super(detailMessage); } } static final class MyAssert { public static void assertCondition(String message, boolean condition) { if (!condition) { throw new MyAssertionError(message); } } } } 

Using this regular TestVerifier rule, your failure trace will only be:

 java.lang.AssertionError: Test failed: verifierTest at RuleTest$TestVerifier.apply(RuleTest.java:26) at org.junit.rules.RunRules.applyAll(RunRules.java:26) ... 

In your IDE, it will look like this:

IDE screen shot

+1
source share

All Articles