Skip to content

Commit 099c75a

Browse files
committed
feat: 100% unit test coverage
1 parent 087696d commit 099c75a

File tree

9 files changed

+604
-92
lines changed

9 files changed

+604
-92
lines changed

pom.xml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,34 @@
3232
<maven.compiler.target>11</maven.compiler.target>
3333
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
3434
</properties>
35+
36+
<dependencies>
37+
<dependency>
38+
<groupId>ch.qos.logback</groupId>
39+
<artifactId>logback-classic</artifactId>
40+
<version>1.4.14</version>
41+
<scope>test</scope>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.junit.jupiter</groupId>
45+
<artifactId>junit-jupiter-engine</artifactId>
46+
<version>5.9.0</version>
47+
<scope>test</scope>
48+
</dependency>
49+
<dependency>
50+
<groupId>org.mockito</groupId>
51+
<artifactId>mockito-core</artifactId>
52+
<version>4.8.0</version>
53+
<scope>test</scope>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.mockito</groupId>
57+
<artifactId>mockito-junit-jupiter</artifactId>
58+
<version>4.8.0</version>
59+
<scope>test</scope>
60+
</dependency>
61+
</dependencies>
62+
3563
<build>
3664
<plugins>
3765
<plugin>

src/main/java/com/cae/mapped_exceptions/MappedException.java

Lines changed: 4 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -6,75 +6,21 @@
66
import java.util.List;
77
import java.util.Optional;
88

9-
/**
10-
* Any exception which does not extend MappedException being
11-
* thrown during a use case execution will be treated as an unexpected
12-
* exception, as in some kind of unexpected error. On the other hand,
13-
* any exception which extends MappedException will be treated as
14-
* exceptions that are part of the workflow of your use case, that means
15-
* use case processors won't intercept them as they will consider it
16-
* part of your planned flow. However, if an exception which does
17-
* not extend MappedException is thrown during a use case execution,
18-
* the use case processors will catch it and handle it as an unexpected
19-
* error during your use case flow, breaking the flow.
20-
* <p></p>
21-
* For instance, consider your use case checks if a user exists before
22-
* performing some action: if the user exists, you perform it and if it
23-
* doesn't, you throw an exception such as 'UserNotFoundException'.
24-
* That exception would be a perfect example of MappedException, as
25-
* it is some expected behavior within your use case execution.
26-
*/
279
public class MappedException extends RuntimeException{
2810

29-
/**
30-
* Brief public message for the exception. If you want to only
31-
* provide some superficial info about the exception, you set this
32-
* attribute with that kind of data. The more detailed info is supposed
33-
* to be found, optionally, in the 'details' field.
34-
*/
3511
protected final String briefPublicMessage;
3612

37-
/**
38-
* Details for the understanding of the exception. In some cases we
39-
* need to, based on an exception, return some friendly message to
40-
* the outside world or something of that kind, while needing to be
41-
* able to log more nasty and dirty details about its cause. This field
42-
* is supposed to keep that kind of data, optionally.
43-
* If the exception doesn't need a more detailed message, just
44-
* don't set it here, be free to just set the brief and public message.
45-
*/
4613
protected final String details;
4714

4815
protected final Exception originalException;
4916

50-
/**
51-
* Constructor method for exceptions that have both a brief,
52-
* public message and more details about its cause. The 'message'
53-
* attribute inherited from the RuntimeException will be set
54-
* concatenating the brief public message with the detailed info.
55-
* @param briefPublicMessage the public and brief message, which
56-
* is supposed to be more friendly and accessible to
57-
* the outside world (such as in the return of a REST
58-
* endpoint, for instance).
59-
* @param details the more detailed info about the cause of the
60-
* exception. That info is supposed to be used in internal
61-
* affairs (such as in logging controls).
62-
*/
6317
public MappedException(String briefPublicMessage, String details){
6418
super(briefPublicMessage + " | " + details);
6519
this.briefPublicMessage = briefPublicMessage;
6620
this.details = details;
6721
this.originalException = null;
6822
}
6923

70-
/**
71-
* Constructor method for exceptions that are just fine with only a
72-
* brief and public message, with no need of a more detailed
73-
* explanation. The 'message' attribute inherited from the
74-
* RuntimeException will be set only containing the brief public
75-
* message.
76-
* @param briefPublicMessage the brief and public info
77-
*/
7824
public MappedException(String briefPublicMessage){
7925
super(briefPublicMessage);
8026
this.briefPublicMessage = briefPublicMessage;
@@ -89,25 +35,13 @@ public MappedException(String briefPublicMessage, String details, Exception orig
8935
this.originalException = originalException;
9036
}
9137

92-
/**
93-
* Constructor method for exceptions that are just fine with only a
94-
* brief and public message, with no need of a more detailed
95-
* explanation. The 'message' attribute inherited from the
96-
* RuntimeException will be set only containing the brief public
97-
* message.
98-
* @param briefPublicMessage the brief and public info
99-
*/
10038
public MappedException(String briefPublicMessage, Exception originalException){
10139
super(briefPublicMessage + " | Original: " + originalException);
10240
this.briefPublicMessage = briefPublicMessage;
10341
this.details = null;
10442
this.originalException = originalException;
10543
}
10644

107-
/**
108-
* Getter method for the brief public message.
109-
* @return the brief and public message available.
110-
*/
11145
public String getBriefPublicMessage(){
11246
return this.briefPublicMessage;
11347
}
@@ -120,6 +54,10 @@ public Optional<String> getDetails() {
12054
return Optional.ofNullable(this.details);
12155
}
12256

57+
public Optional<Exception> getOriginalException(){
58+
return Optional.ofNullable(this.originalException);
59+
}
60+
12361
public List<String> getLinesFromStackTraceFromOriginalException(Integer numberOfLines){
12462
return Optional.ofNullable(this.originalException)
12563
.map(originalE -> this.getLinesFromStackTraceAsString(originalE, numberOfLines))

src/main/java/com/cae/mapped_exceptions/specifics/InputMappedException.java

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,12 @@
33

44
import com.cae.mapped_exceptions.MappedException;
55

6-
/**
7-
* More specific type of MappedException thought to cover cases in
8-
* which something came in wrong into your application. In HTTP, for
9-
* example, it would be represented by 4xx status code.
10-
*/
116
public class InputMappedException extends MappedException {
127

13-
/**
14-
* Constructor method for exceptions that have both a brief,
15-
* public message and more details about its cause. The 'message'
16-
* attribute inherited from the RuntimeException will be set
17-
* concatenating the brief public message with the detailed info.
18-
* @param briefPublicMessage the public and brief message, which
19-
* is supposed to be more friendly and accessible to
20-
* the outside world (such as in the return of a REST
21-
* endpoint, for instance).
22-
* @param details the more detailed info about the cause of the
23-
* exception. That info is supposed to be used in internal
24-
* affairs (such as in logging controls).
25-
*/
268
public InputMappedException(String briefPublicMessage, String details){
279
super(briefPublicMessage, details);
2810
}
2911

30-
/**
31-
* Constructor method for exceptions that are just fine with only a
32-
* brief and public message, with no need of a more detailed
33-
* explanation. The 'message' attribute inherited from the
34-
* RuntimeException will be set only containing the brief public
35-
* message.
36-
* @param briefPublicMessage the brief and public info
37-
*/
3812
public InputMappedException(String briefPublicMessage){
3913
super(briefPublicMessage);
4014
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package com.cae.mapped_exceptions;
2+
3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.extension.ExtendWith;
6+
import org.mockito.junit.jupiter.MockitoExtension;
7+
8+
import java.io.PrintWriter;
9+
import java.io.StringWriter;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
13+
@ExtendWith(MockitoExtension.class)
14+
class MappedExceptionTest {
15+
16+
@Test
17+
void shouldReturnTheBriefPublicMessageSet(){
18+
var briefPublicMessage = "That's a public, brief, message of the exception";
19+
var testSubject = new TestSubjectException(briefPublicMessage);
20+
Assertions.assertEquals(briefPublicMessage, testSubject.getBriefPublicMessage());
21+
}
22+
23+
@Test
24+
void shouldNotSetDetailsWhenOnlyTheBriefPublicMessageWasSet(){
25+
var briefPublicMessage = "That's a public, brief, message of the exception again";
26+
var testSubject = new TestSubjectException(briefPublicMessage);
27+
Assertions.assertFalse(testSubject.getDetails().isPresent());
28+
}
29+
30+
@Test
31+
void shouldReturnTheDetailsSet(){
32+
var briefPublicMessage = "Once again, a public, brief, message of the exception";
33+
var details = "woah! These are some nasty details that should be kept internally available";
34+
var testSubject = new TestSubjectException(briefPublicMessage, details);
35+
Assertions.assertEquals(briefPublicMessage, testSubject.getBriefPublicMessage());
36+
Assertions.assertTrue(testSubject.getDetails().isPresent());
37+
Assertions.assertEquals(details, testSubject.getDetails().get());
38+
}
39+
40+
@Test
41+
void shouldSetTheFullMessageCorrectlyWhenOnlyABriefMessageIsSet(){
42+
var briefPublicMessage = "Once again, a public, brief, message of the exception";
43+
var testSubject = new TestSubjectException(briefPublicMessage);
44+
Assertions.assertEquals(briefPublicMessage, testSubject.getMessage());
45+
}
46+
47+
@Test
48+
void shouldSetTheFullMessageCorrectlyWhenBothBriefAndDetailsTextsAreSet(){
49+
var briefPublicMessage = "There we go one more time: another public, brief, message of the exception";
50+
var details = "Even nastier details that should be kept internally available";
51+
var testSubject = new TestSubjectException(briefPublicMessage, details);
52+
Assertions.assertEquals(briefPublicMessage + " | " + details, testSubject.getMessage());
53+
}
54+
55+
@Test
56+
void shouldReturnTheOriginalExceptionSet(){
57+
var originalException = new RuntimeException("Opsie... hi-hi-hi");
58+
var briefPublicMessage = "Well... at least that's a fully new kind of scenario for me, a brief, public message of a mapped exception";
59+
var testSubject = new TestSubjectException(briefPublicMessage, originalException);
60+
Assertions.assertTrue(testSubject.getOriginalException().isPresent());
61+
Assertions.assertEquals(originalException, testSubject.getOriginalException().get());
62+
}
63+
64+
@Test
65+
void shouldCorrectlySetTheGeneralMessageWhenTheOriginalExceptionSet(){
66+
var originalException = new RuntimeException("Opsie... hi-hi-hi");
67+
var briefPublicMessage = "Well... that's a kinda of new scenario for me still";
68+
var testSubject = new TestSubjectException(briefPublicMessage, originalException);
69+
Assertions.assertEquals(briefPublicMessage + " | Original: " + originalException, testSubject.getMessage());
70+
}
71+
72+
@Test
73+
void shouldCorrectlySetTheGeneralMessageWhenBothTheOriginalExceptionAndDetailsAreSet(){
74+
var originalException = new RuntimeException("Opsie... hi-hi-hi");
75+
var briefPublicMessage = "Okay...";
76+
var details = "Hello world! ^^";
77+
var testSubject = new TestSubjectException(briefPublicMessage, details, originalException);
78+
Assertions.assertEquals(briefPublicMessage + " | " + details + " | Original: " + originalException, testSubject.getMessage());
79+
}
80+
81+
@Test
82+
void shouldNotSetAnyOriginalExceptionWhenNoneIsProvided(){
83+
var briefPublicMessage = "Right...";
84+
var details = "Olá mundo! ><";
85+
var testSubjectWithDetails = new TestSubjectException(briefPublicMessage, details);
86+
var testSubjectWithoutDetails = new TestSubjectException(briefPublicMessage);
87+
Assertions.assertFalse(testSubjectWithoutDetails.getOriginalException().isPresent());
88+
Assertions.assertFalse(testSubjectWithDetails.getOriginalException().isPresent());
89+
}
90+
91+
@Test
92+
void shouldReturnStackTraceFromItselfAsExpected(){
93+
var briefPublicMessage = "Oh boy";
94+
var testSubjectWithoutDetails = new TestSubjectException(briefPublicMessage);
95+
var numberOfLines = 5;
96+
var expectedLinesOfStackTrace = this.getLines(testSubjectWithoutDetails, numberOfLines);
97+
var actualLinesRetrieved = testSubjectWithoutDetails.getLinesFromStackTrace(numberOfLines);
98+
expectedLinesOfStackTrace.forEach(line -> Assertions.assertTrue(actualLinesRetrieved.stream().anyMatch(actualLine -> actualLine.equals(line))));
99+
}
100+
101+
@Test
102+
void shouldReturnStackTraceFromOriginalExceptionAsExpected(){
103+
var originalException = new RuntimeException("Opsie... hi-hi-hi");
104+
var briefPublicMessage = "Here we go again";
105+
var testSubjectWithoutDetails = new TestSubjectException(briefPublicMessage, originalException);
106+
var numberOfLines = 5;
107+
var expectedLinesOfStackTrace = this.getLines(originalException, numberOfLines);
108+
var actualLinesRetrieved = testSubjectWithoutDetails.getLinesFromStackTraceFromOriginalException(numberOfLines);
109+
expectedLinesOfStackTrace.forEach(line -> Assertions.assertTrue(actualLinesRetrieved.stream().anyMatch(actualLine -> actualLine.equals(line))));
110+
}
111+
112+
@Test
113+
void shouldReturnStackTraceAsStringAsExpected(){
114+
var briefPublicMessage = "When will this end?";
115+
var testSubjectWithoutDetails = new TestSubjectException(briefPublicMessage);
116+
var expectedFullStackTraceAsString = this.getFullStackTraceAsString(testSubjectWithoutDetails);
117+
var actualFullStackTraceAsString = testSubjectWithoutDetails.getFullStackTraceAsString();
118+
Assertions.assertEquals(expectedFullStackTraceAsString, actualFullStackTraceAsString);
119+
}
120+
121+
@Test
122+
void shouldNotBreakIfNumberOfLinesRequestedAreGreaterThanActualAvailableLines(){
123+
var briefPublicMessage = "Finally";
124+
var testSubjectWithoutDetails = new TestSubjectException(briefPublicMessage);
125+
var numberOfLinesWayGreaterThanItShouldBe = 100000;
126+
Assertions.assertDoesNotThrow(() -> testSubjectWithoutDetails.getLinesFromStackTraceFromOriginalException(numberOfLinesWayGreaterThanItShouldBe));
127+
}
128+
129+
public static class TestSubjectException extends MappedException{
130+
131+
public TestSubjectException(String briefPublicMessage, String details) {
132+
super(briefPublicMessage, details);
133+
}
134+
135+
public TestSubjectException(String briefPublicMessage) {
136+
super(briefPublicMessage);
137+
}
138+
139+
public TestSubjectException(String briefPublicMessage, String details, Exception originalException) {
140+
super(briefPublicMessage, details, originalException);
141+
}
142+
143+
public TestSubjectException(String briefPublicMessage, Exception originalException) {
144+
super(briefPublicMessage, originalException);
145+
}
146+
}
147+
148+
private List<String> getLines(Exception exception, Integer numberOfLines){
149+
var allLines = List.of(exception.getStackTrace());
150+
var linesToReturn = new ArrayList<String>();
151+
var counter = 0;
152+
var maxCount = allLines.size() < numberOfLines? allLines.size() : numberOfLines;
153+
while (counter < maxCount){
154+
var line = allLines.get(counter);
155+
linesToReturn.add(line.toString());
156+
counter ++;
157+
}
158+
if (allLines.size() > numberOfLines){
159+
var lastLine = allLines.size() - numberOfLines + " hidden line(s)";
160+
linesToReturn.add(lastLine);
161+
}
162+
return linesToReturn;
163+
}
164+
165+
private String getFullStackTraceAsString(MappedException mappedException){
166+
var stringWriter = new StringWriter();
167+
mappedException.printStackTrace(new PrintWriter(stringWriter));
168+
return stringWriter.toString();
169+
}
170+
171+
}
172+

0 commit comments

Comments
 (0)