Skip to content

Commit

Permalink
JMockit NonStrictExpectations with times should be migrated to defaul…
Browse files Browse the repository at this point in the history
…t when without lenient(), refactor (#549)

* Add migration of JMockit NonStrictExpectations blocks as well as JMockit junit 4 runner

* Add unit tests for NonStrictExpectations, all passing, refactor to reduce code duplication

* Add missing copyright clause

* Remove @nullable annotation because using javax or open rewrite internal annotation is not recommended as per code review

* Add missing nullable annotations

* When putting times in NonStrictExpectations ensure it is strict stubbing and add test cases, also refactor

* Refactor logic for imports to make it cleaner

* Qualify `rewriteFailed` consistently

---------

Co-authored-by: Tim te Beek <tim@moderne.io>
  • Loading branch information
shivanisky and timtebeek committed Jul 11, 2024
1 parent a79dc45 commit 5a2d834
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ boolean isRewriteFailed() {
this.newExpectations = newExpectations;
this.bodyStatementIndex = bodyStatementIndex;
this.blockType = blockType;
nextStatementCoordinates = newExpectations.getCoordinates().replace();
this.nextStatementCoordinates = newExpectations.getCoordinates().replace();
}

J.Block rewriteMethodBody() {
Expand Down Expand Up @@ -125,74 +125,63 @@ private void rewriteMethodInvocation(List<Statement> statementsToRewrite) {
final MockInvocationResults mockInvocationResults = buildMockInvocationResults(statementsToRewrite);
if (mockInvocationResults == null) {
// invalid block, cannot rewrite
rewriteFailed = true;
this.rewriteFailed = true;
return;
}

J.MethodInvocation invocation = (J.MethodInvocation) statementsToRewrite.get(0);
boolean hasResults = !mockInvocationResults.getResults().isEmpty();
boolean hasTimes = mockInvocationResults.hasAnyTimes();
if (hasResults) {
rewriteResult(invocation, mockInvocationResults.getResults());
rewriteResult(invocation, mockInvocationResults.getResults(), hasTimes);
}

if (blockType == NonStrictExpectations) {
// no verify for NonStrictExpectations
if (!hasResults && !hasTimes && (this.blockType == JMockitBlockType.Expectations || this.blockType == Verifications)) {
rewriteVerify(invocation, null, "");
return;
}

boolean hasTimes = false;
if (mockInvocationResults.getTimes() != null) {
hasTimes = true;
rewriteVerify(invocation, mockInvocationResults.getTimes(), "times");
}
if (mockInvocationResults.getMinTimes() != null) {
hasTimes = true;
rewriteVerify(invocation, mockInvocationResults.getMinTimes(), "atLeast");
}
if (mockInvocationResults.getMaxTimes() != null) {
hasTimes = true;
rewriteVerify(invocation, mockInvocationResults.getMaxTimes(), "atMost");
}
if (!hasResults && !hasTimes) {
rewriteVerify(invocation, null, "");
}
}

private void removeBlock() {
methodBody = JavaTemplate.builder("")
.javaParser(JavaParser.fromJavaVersion())
.build()
.apply(new Cursor(visitor.getCursor(), methodBody), nextStatementCoordinates);
if (bodyStatementIndex == 0) {
nextStatementCoordinates = methodBody.getCoordinates().firstStatement();
} else {
setNextStatementCoordinates(0);
}
setNextStatementCoordinates(0);
}

private void rewriteResult(J.MethodInvocation invocation, List<Expression> results) {
String template = getWhenTemplate(results);
private void rewriteResult(J.MethodInvocation invocation, List<Expression> results, boolean hasTimes) {
boolean lenient = this.blockType == NonStrictExpectations && !hasTimes;
String template = getWhenTemplate(results, lenient);
if (template == null) {
// invalid template, cannot rewrite
rewriteFailed = true;
this.rewriteFailed = true;
return;
}

List<Object> templateParams = new ArrayList<>();
templateParams.add(invocation);
templateParams.addAll(results);
this.rewriteFailed = !rewriteTemplate(template, templateParams, nextStatementCoordinates);
if (!this.rewriteFailed) {
this.rewriteFailed = true;
setNextStatementCoordinates(++numStatementsAdded);
// do this last making sure rewrite worked and specify hasReference=false because framework cannot find static
// reference for when method invocation when lenient is added.
boolean hasReferencesForWhen = true;
if (this.blockType == NonStrictExpectations) {
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "lenient");
hasReferencesForWhen = false;
}
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "when", hasReferencesForWhen);
if (this.rewriteFailed) {
return;
}

setNextStatementCoordinates(++numStatementsAdded);
// do this last making sure rewrite worked and specify onlyifReferenced=false because framework cannot find static
// reference for when method invocation when another static mockit reference is added
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "when", false);
if (lenient) {
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "lenient");
}
}

Expand All @@ -218,29 +207,37 @@ private void rewriteVerify(J.MethodInvocation invocation, @Nullable Expression t
verifyCoordinates = methodBody.getCoordinates().lastStatement();
}
this.rewriteFailed = !rewriteTemplate(verifyTemplate, templateParams, verifyCoordinates);
if (!this.rewriteFailed) {
if (this.blockType == Verifications) {
setNextStatementCoordinates(++numStatementsAdded); // for Expectations, verify statements added to end of method
}
if (this.rewriteFailed) {
return;
}

// do this last making sure rewrite worked and specify hasReference=false because in verify case framework
// cannot find the static reference
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "verify", false);
if (!verificationMode.isEmpty()) {
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, verificationMode);
}
if (this.blockType == Verifications) {
setNextStatementCoordinates(++numStatementsAdded); // for Expectations, verify statements added to end of method
}

// do this last making sure rewrite worked and specify onlyifReferenced=false because framework cannot find the
// static reference to verify when another static mockit reference is added
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "verify", false);
if (!verificationMode.isEmpty()) {
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, verificationMode);
}
}

private void setNextStatementCoordinates(int numStatementsAdded) {
if (numStatementsAdded <= 0 && bodyStatementIndex == 0) {
nextStatementCoordinates = methodBody.getCoordinates().firstStatement();
return;
}

// the next statement coordinates are directly after the most recently written statement, calculated by
// subtracting the removed jmockit block
int nextStatementIdx = bodyStatementIndex + numStatementsAdded - 1;
if (nextStatementIdx >= this.methodBody.getStatements().size()) {
rewriteFailed = true;
} else {
this.nextStatementCoordinates = this.methodBody.getStatements().get(nextStatementIdx).getCoordinates().after();
int lastStatementIdx = bodyStatementIndex + numStatementsAdded - 1;
if (lastStatementIdx >= this.methodBody.getStatements().size()) {
this.rewriteFailed = true;
return;
}

this.nextStatementCoordinates = this.methodBody.getStatements().get(lastStatementIdx).getCoordinates().after();
}

private boolean rewriteTemplate(String template, List<Object> templateParams, JavaCoordinates
Expand All @@ -258,10 +255,10 @@ private boolean rewriteTemplate(String template, List<Object> templateParams, Ja
return methodBody.getStatements().size() > numStatementsBefore;
}

private @Nullable String getWhenTemplate(List<Expression> results) {
private @Nullable String getWhenTemplate(List<Expression> results, boolean lenient) {
boolean buildingResults = false;
StringBuilder templateBuilder = new StringBuilder();
if (this.blockType == NonStrictExpectations) {
if (lenient) {
templateBuilder.append(LENIENT_TEMPLATE_PREFIX);
}
templateBuilder.append(WHEN_TEMPLATE_PREFIX);
Expand Down Expand Up @@ -422,5 +419,9 @@ private static class MockInvocationResults {
private void addResult(Expression result) {
results.add(result);
}

private boolean hasAnyTimes() {
return times != null || minTimes != null || maxTimes != null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,61 @@ public void defaults(RecipeSpec spec) {
setDefaultParserSettings(spec);
}

@DocumentExample
@Test
void whenTimesAndResult() {
//language=java
rewriteRun(
java(
"""
import mockit.Expectations;
import mockit.Mocked;
import mockit.integration.junit5.JMockitExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(JMockitExtension.class)
class MyTest {
@Mocked
Object myObject;
void test() {
new Expectations() {{
myObject.toString();
result = "foo";
times = 2;
}};
assertEquals("foo", myObject.toString());
assertEquals("foo", myObject.toString());
}
}
""",
"""
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class MyTest {
@Mock
Object myObject;
void test() {
when(myObject.toString()).thenReturn("foo");
assertEquals("foo", myObject.toString());
assertEquals("foo", myObject.toString());
verify(myObject, times(2)).toString();
}
}
"""
)
);
}

@DocumentExample
@Test
void whenNoResultNoTimes() {
Expand Down Expand Up @@ -77,7 +132,6 @@ void test() {
);
}

@DocumentExample
@Test
void whenNoResultNoTimesNoArgs() {
//language=java
Expand Down Expand Up @@ -124,6 +178,56 @@ void test() {
);
}

@Test
void whenHasResultNoTimes() {
//language=java
rewriteRun(
java(
"""
import mockit.Expectations;
import mockit.Mocked;
import mockit.integration.junit5.JMockitExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(JMockitExtension.class)
class MyTest {
@Mocked
Object myObject;
void test() {
new Expectations() {{
myObject.toString();
result = "foo";
}};
assertEquals("foo", myObject.toString());
}
}
""",
"""
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class MyTest {
@Mock
Object myObject;
void test() {
when(myObject.toString()).thenReturn("foo");
assertEquals("foo", myObject.toString());
}
}
"""
)
);
}

@Test
void whenNullResult() {
//language=java
Expand Down
Loading

0 comments on commit 5a2d834

Please sign in to comment.