-2

My Question is how to use Mockito's doAnswer to call the original method and with my own test parameters. To complicate matters, one of the parameters is not a primitive. It's a custom class.

Given the following:

public class EnvironmentQualityStatus {
    public int environmentQuality = EnvironmentQualityStatus.NA;
    public long cht = -1;
    public int environmentSubType = -1;
    public String engineer="";
    public int engineerType = -1;
    public long timeStamp = 0;
    public static final String version = "tpv1";

    EnvironmentQualityStatus(){}

    EnvironmentQualityStatus(int environmentQuality, long cht, int environmentSubType, String engineer, int engineerType) {
        this.environmentQuality = environmentQuality;
        this.cht = cht;
        this.environmentSubType = environmentSubType;
        this.engineer = engineer;
        this.engineerType = engineerType;
        timeStamp = System.currentTimeMillis();
     }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("ts: ");
        sb.append(timeStamp);
        sb.append(", quality: ");
        sb.append(EnvironmentQualityStatus.valueOf(environmentQuality));
        sb.append(", cht: ");
        sb.append(cht);
        sb.append(", type: ");
        if (engineerType == Engineers.TYPE_CIVIL) {
            sb.append(“civil”);
        } else if (engineerType == Engineers.TYPE_INDUSTRIAL) {
            sb.append(“industrial”);
            if (!TextUtils.isEmpty(engineer)) {
                sb.append(", Engineer: ");
                sb.append(engineer);
             }
         } else {
             sb.append("n/a");
         }
         return sb.toString();
    }
}

Taking into use the code above to set some stuff in a class. It's this call (and others like this) that I'd like to mock using Mockito.

public class EnvironmentQuality {
...
public void updateEnvironmentQuality(EnvironmentQualityStatus eqStatus ,
                                      int environmentQuality ,int cht , String engineer, boolean notify ){
        //set some stuff....
    }
}

I understand that using Mockito's doAnswer is the right way to do this. I just can't figure out how to wire up the very last part, where I manipulate the called values with my own so that the program that uses them at test-run-time gets my values. I've read Mockito docs as well as several SO posts around this topic, but it's still not clear to me.

EnvironmentQuality environmentQuality = spy((EnvironmentQuality)EnvironmentQuality.getInstance(context));

    spy(environmentQuality).updateNetworkQuality(Mockito.any(com.something.internal.EnvironmentQualityStatus.class), anyInt(), anyInt(), anyString(), anyBoolean());
    doAnswer(new Answer<Void>() {
        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {

            //This is an example of how to spy the parameters that were passed.
            EnvironmentQualityStatus status = (EnvironmentQualityStatus)invocation.getArguments()[0];

            int environmentQuality = (int)invocation.getArguments()[1];
            int cht = (int)invocation.getArguments()[2];
            String engineer = (String)invocation.getArguments()[3];
            boolean notify = (boolean)invocation.getArguments()[4];

            //But how to customize the parameters so that every time updateEnvironmentQuality(...) is called, a test can customize the params and dynamically influence the values.
            return null;
        }
    }).when(environmentQuality).updateEnvironmentQuality(Mockito.any(com.something.internal.EnvironmentQualityStatus.class), anyInt(), anyInt(), anyString(), anyBoolean());
Michael Lupo
  • 326
  • 3
  • 17
  • 1
    you can update the EnvironmentQualityStatus param only within the doAnswer, as the rest are immutable. – Maciej Kowalski Aug 02 '17 at 12:23
  • 1
    Why the -1? I think that the question and your answer could clarify the same to someone else. – Michael Lupo Aug 02 '17 at 12:31
  • 1
    Your question is not clear to me. Are you saying you want the real version of the stubbed method to be called as well, but with modified values for the parameters? I don't believe Mockito does this currently, but you could put in a feature request. – Dawood ibn Kareem Aug 02 '17 at 12:42
  • @DawoodibnKareem, Thanks for your add. I have updated the question in hopes of clarifying it for all. – Michael Lupo Aug 02 '17 at 12:45

1 Answers1

1

So I assume you want to invoke the method with other values. By checking the documentation of InvocationOnMock you can check how to get the Method and the Object that is called. So this should be quite easy to modify:

    @Override
    public Void answer(InvocationOnMock invocation) throws Throwable {
        Object callingObject = invocation.getMock();
        Method method = invocation.getMethod();
        method.invoke(callingObject, /* arguments... */);
        return null;
    }
Alberto S.
  • 7,409
  • 6
  • 27
  • 46
  • I'd like to add to this. In the case where you're trying to proxy the method (like I'm doing) we were ending up in a recursive call situation. When we'd call the method with our own parameters, we'd end up back in the Answer again. So the workaround to this is to use reflection to get at the member variables that the setter method was setting. Hence, the outcome (for us) is the same without the problem of recursion. Your mileage may vary. – Michael Lupo Aug 04 '17 at 12:17
  • Have you tested this? I'm not sure it will work. According to the Javadoc for the `invoke` method of `Method`, _"... it is invoked using dynamic method lookup as documented in The Java Language Specification, Second Edition, section 15.12.4.4; in particular, overriding based on the runtime type of the target object will occur ..."_ This means that you're just invoking the method again on the stubbed object. So your `answer` method will call itself repeatedly until your stack overflows. – Dawood ibn Kareem Aug 04 '17 at 19:56