Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add names and types of method parameters in error output #62

Merged
merged 9 commits into from
Oct 11, 2024
37 changes: 20 additions & 17 deletions force-app/src/classes/Argument.cls
Original file line number Diff line number Diff line change
Expand Up @@ -131,25 +131,23 @@ global class Argument {
}

override public String toString() {
return callArgumentToMatch + '';
return this.callArgumentToMatch + '';
}
}

private class JSONMatchable implements Argument.Matchable {
private Object callArgumentToMatch;
private String jsonValue;

public JSONMatchable(final Object callArgumentToMatch) {
this.callArgumentToMatch = callArgumentToMatch;
this.jsonValue = JSON.serialize(callArgumentToMatch);
}

public boolean matches(final Object callArgument) {
return this.jsonValue == JSON.serialize(callArgument);
return String.valueOf(this.jsonValue).equals(String.valueOf(JSON.serialize(callArgument)));
}

override public String toString() {
return 'json(' + callArgumentToMatch + ')';
return 'json(' + this.jsonValue + ')';
}
}

Expand All @@ -169,7 +167,7 @@ global class Argument {
}

public boolean matches(final Object callArgument) {
String typeName = this.getType(callArgument);
String typeName = Argument.getTypeName(callArgument);
if (this.callArgumentToMatch == typeName) {
return true;
}
Expand All @@ -182,19 +180,24 @@ global class Argument {
return false;
}

private String getType(final Object callArgument) {
String result = 'Date';
try {
Date typeCheck = (Date) callArgument;
} catch (System.TypeException te) {
String message = te.getMessage().substringAfter('Invalid conversion from runtime type ');
result = message.substringBefore(' to Date');
}
return result;
override public String toString() {
return this.callArgumentToMatch + '.Type';
}
}

override public String toString() {
return callArgumentToMatch + '.Type';
private static String getTypeName(final Object callArgument) {
String result = 'Date';
try {
Date typeCheck = (Date) callArgument;
} catch (System.TypeException te) {
String message = te.getMessage().substringAfter('Invalid conversion from runtime type ');
result = message.substringBefore(' to Date');
}
return result;
}

public static Type getType(final Object callArgument) {
final String typeName = getTypeName(callArgument);
return Type.forName(typeName);
}
}
48 changes: 44 additions & 4 deletions force-app/src/classes/MethodSpy.cls
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,28 @@ global class MethodSpy {
this.callLog = new CallLog();
}

// @deprecated use call(List<Type> paramTypes, List<String> paramNames, List<Object> args) instead
public Object call(List<Object> args) {
// Forward compatibility implementation
List<Type> paramTypes = new List<Type>();
List<String> paramNames = new List<String>();

for (Object arg : args) {
paramTypes.add(Argument.getType(arg)); // Infer type dynamically
paramNames.add('<unknown_param_name>'); // Impossible to get this information at runtime
}

// Warn consumer
System.Debug(
LoggingLevel.WARN,
'[apex-mockery] call to the deprecated MethodSpy.call(List<Object> args) method.\nPlease use MethodSpy.call(List<Type> paramTypes, List<String> paramNames, List<Object> args) instead.'
);

// Interoperability
return this.call(paramTypes, paramNames, args);
}

public Object call(List<Type> paramTypes, List<String> paramNames, List<Object> args) {
this.callLog.add(new MethodCall(args));

if (this.parameterizedMethodCalls.isEmpty() && !this.configuredGlobalReturn && !this.configuredGlobalThrow) {
Expand All @@ -53,7 +74,7 @@ global class MethodSpy {
throw this.exceptionToThrow;
}

throw new ConfigurationExceptionBuilder().withMethodSpy(this).withCallArguments(args).build();
throw new ConfigurationExceptionBuilder().withMethodSpy(this).withCallTypes(paramTypes).withCallParamNames(paramNames).withCallArguments(args).build();
}

global void returns(Object value) {
Expand Down Expand Up @@ -167,13 +188,25 @@ global class MethodSpy {

private class ConfigurationExceptionBuilder {
private MethodSpy spy;
private List<Type> callTypes;
private List<String> callParamNames;
private List<Object> callArguments;

public ConfigurationExceptionBuilder withMethodSpy(final MethodSpy spy) {
this.spy = spy;
return this;
}

public ConfigurationExceptionBuilder withCallTypes(final List<Type> callTypes) {
this.callTypes = callTypes;
return this;
}

public ConfigurationExceptionBuilder withCallParamNames(final List<String> callParamNames) {
this.callParamNames = callParamNames;
return this;
}

public ConfigurationExceptionBuilder withCallArguments(final List<Object> callArguments) {
this.callArguments = callArguments;
return this;
Expand All @@ -184,10 +217,17 @@ global class MethodSpy {
for (ParameterizedMethodSpyCall parameterizedCall : this.spy.parameterizedMethodCalls) {
errorMessages.add(parameterizedCall.toString());
}

List<String> callArgumentsAsString = new List<String>();
for (Integer i = 0; i < this.callArguments.size(); i++) {
callArgumentsAsString.add(this.callTypes[i] + ' ' + this.callParamNames[i] + '[' + this.callArguments[i] + ']');
}
return new ConfigurationException(
this.spy.methodName +
': No stub value found for a call with args ' +
this.callArguments +
'No stub value found for a call of ' +
this.spy.methodName +
'(' +
String.join(callArgumentsAsString, ', ') +
')' +
'\nHere are the configured stubs:\n\t' +
String.join(errorMessages, '\n\t')
);
Expand Down
2 changes: 1 addition & 1 deletion force-app/src/classes/Mock.cls
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ global class Mock implements System.StubProvider {
Object result;
if (this.spies.containsKey(stubbedMethodName)) {
MethodSpy spy = this.getSpy(stubbedMethodName);
result = spy.call(listOfArgs);
result = spy.call(listOfParamTypes, listOfParamNames, listOfArgs);
}

return result;
Expand Down
32 changes: 32 additions & 0 deletions force-app/test/package/classes/unit/ArgumentTest.cls
Original file line number Diff line number Diff line change
Expand Up @@ -565,4 +565,36 @@ public class ArgumentTest {
return true;
}
}

@IsTest
static void getType_whenCalledWithSObject_returnsSObjectType() {
// Assert
Assert.areEqual(Account.class, Argument.getType(new Account()));
Assert.areEqual(Opportunity.class, Argument.getType(new Opportunity()));
}

@IsTest
static void getType_whenCalledWithPrimitive_returnsPrimitiveType() {
// Assert
Assert.areEqual(Blob.class, Argument.getType(Blob.valueOf('test')));
Assert.areEqual(Boolean.class, Argument.getType(true));
Assert.areEqual(Date.class, Argument.getType(Date.today()));
Assert.areEqual(Datetime.class, Argument.getType(Datetime.now()));
Decimal dec = 42.0;
Assert.areEqual(Decimal.class, Argument.getType(dec));
Assert.areEqual(Double.class, Argument.getType(42.0d));
Id contactId = '00300000003T2PGAA0';
Assert.areEqual(ID.class, Argument.getType(contactId));
Assert.areEqual(Integer.class, Argument.getType(42));
Assert.areEqual(Long.class, Argument.getType(42L));
Assert.areEqual(String.class, Argument.getType('test'));
Assert.areEqual(Time.class, Argument.getType(Time.newInstance(10, 10, 10, 10)));
}

@IsTest
static void getType_whenCalledWithObject_returnObjectType() {
// Assert
Assert.areEqual(List<Account>.class, Argument.getType(new List<Account>()));
Assert.areEqual(ArgumentTest.class, Argument.getType(new ArgumentTest()));
}
}
54 changes: 39 additions & 15 deletions force-app/test/package/classes/unit/ExpectTest.cls
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ 'param' });
spy.call(new List<Type>{ String.class }, new List<String>{ 'name' }, new List<Object>{ 'param' });

// Act
sut.hasBeenCalledWith(Argument.of('param'));
Expand All @@ -157,7 +157,7 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ '2', 'param' });
spy.call(new List<Type>{ Integer.class, String.class }, new List<String>{ 'count', 'name' }, new List<Object>{ '2', 'param' });

// Act
sut.hasBeenCalledWith('2', 'param');
Expand All @@ -174,7 +174,11 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ '3', 'param', 'test' });
spy.call(
new List<Type>{ Integer.class, String.class, String.class },
new List<String>{ 'count', 'name', 'otherName' },
new List<Object>{ '3', 'param', 'test' }
);

// Act
sut.hasBeenCalledWith('3', 'param', 'test');
Expand All @@ -191,7 +195,11 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ '4', 'param', 'unit', 'test' });
spy.call(
new List<Type>{ Integer.class, String.class, String.class, String.class },
new List<String>{ 'count', 'name', 'otherName', 'yetAnotherName' },
new List<Object>{ '4', 'param', 'unit', 'test' }
);

// Act
sut.hasBeenCalledWith('4', 'param', 'unit', 'test');
Expand All @@ -208,7 +216,11 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ '5', 'param', 'unit', 'test', 'scenario' });
spy.call(
new List<Type>{ Integer.class, String.class, String.class, String.class, String.class },
new List<String>{ 'count', 'name', 'otherName', 'yetAnotherName', 'latestArgument' },
new List<Object>{ '5', 'param', 'unit', 'test', 'scenario' }
);

// Act
sut.hasBeenCalledWith('5', 'param', 'unit', 'test', 'scenario');
Expand All @@ -225,7 +237,7 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ 'param' });
spy.call(new List<Type>{ String.class }, new List<String>{ 'name' }, new List<Object>{ 'param' });

// Act
sut.hasBeenCalledWith(Argument.equals('param'));
Expand All @@ -241,7 +253,7 @@ private class ExpectTest {
MethodSpy spy = new MethodSpy('method');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>());
spy.call(new List<Type>{}, new List<String>{}, new List<Object>{});

// Act
sut.hasBeenCalledWith(null);
Expand Down Expand Up @@ -322,7 +334,7 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ 'param' });
spy.call(new List<Type>{ String.class }, new List<String>{ 'name' }, new List<Object>{ 'param' });

// Act
sut.hasBeenLastCalledWith(Argument.of('param'));
Expand All @@ -339,7 +351,7 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ '2', 'param' });
spy.call(new List<Type>{ Integer.class, String.class }, new List<String>{ 'count', 'name' }, new List<Object>{ '2', 'param' });

// Act
sut.hasBeenLastCalledWith('2', 'param');
Expand All @@ -356,7 +368,11 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ '3', 'param', 'test' });
spy.call(
new List<Type>{ Integer.class, String.class, String.class },
new List<String>{ 'count', 'name', 'anotherName' },
new List<Object>{ '3', 'param', 'test' }
);

// Act
sut.hasBeenLastCalledWith('3', 'param', 'test');
Expand All @@ -373,7 +389,11 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ '4', 'param', 'unit', 'test' });
spy.call(
new List<Type>{ Integer.class, String.class, String.class, String.class },
new List<String>{ 'count', 'name', 'anotherName', 'yetAnotherName' },
new List<Object>{ '4', 'param', 'unit', 'test' }
);

// Act
sut.hasBeenLastCalledWith('4', 'param', 'unit', 'test');
Expand All @@ -390,7 +410,11 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ '5', 'param', 'unit', 'test', 'scenario' });
spy.call(
new List<Type>{ Integer.class, String.class, String.class, String.class, String.class },
new List<String>{ 'count', 'name', 'anotherName', 'yetAnotherName', 'lastParameter' },
new List<Object>{ '5', 'param', 'unit', 'test', 'scenario' }
);

// Act
sut.hasBeenLastCalledWith('5', 'param', 'unit', 'test', 'scenario');
Expand All @@ -407,7 +431,7 @@ private class ExpectTest {
spy.returns('anything');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>{ 'param' });
spy.call(new List<Type>{ String.class }, new List<String>{ 'name' }, new List<Object>{ 'param' });

// Act
sut.hasBeenLastCalledWith(Argument.equals('param'));
Expand All @@ -423,7 +447,7 @@ private class ExpectTest {
MethodSpy spy = new MethodSpy('method');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>());
spy.call(new List<Type>{}, new List<String>{}, new List<Object>());

// Act
sut.hasBeenLastCalledWith(null);
Expand All @@ -439,7 +463,7 @@ private class ExpectTest {
MethodSpy spy = new MethodSpy('method');
FakeAsserter fakeAsserter = new FakeAsserter();
Expect.MethodSpyExpectable sut = Expect.that(spy, fakeAsserter);
spy.call(new List<Object>());
spy.call(new List<Type>{}, new List<String>{}, new List<Object>{});

// Act & Assert
sut.hasBeenCalledTimes(1);
Expand Down
Loading