-3

I have a ViewModel in which there is a method which has the following line of code:

billDate.set(!TextUtils.isEmpty(SampleApp.getInstance().getAccountManager().getDueDate()) ?
            String.format(SampleApp.getInstance().getApplicationContext().getString(R.string.due),
                    SampleApp.getInstance().getAccountManager().getBillingDueDate()) :
            SampleApp.getInstance().getApplicationContext().getString(R.string.missing_due_date));

I have a test class using Mockito to test the different methods in ViewModel. But it is failing with NullPointerException at this line:

String.format(SampleApp.getInstance().getApplicationContext().getString(R.string.due),

Below is the log:

java.lang.NullPointerException
at java.util.regex.Matcher.getTextLength(Matcher.java:1283)
at java.util.regex.Matcher.reset(Matcher.java:309)
at java.util.regex.Matcher.<init>(Matcher.java:229)
at java.util.regex.Pattern.matcher(Pattern.java:1093)
at java.util.Formatter.parse(Formatter.java:2547)
at java.util.Formatter.format(Formatter.java:2501)
at java.util.Formatter.format(Formatter.java:2455)
at java.lang.String.format(String.java:2940)

While running a test case, I see the log showing some error related to Pattern Can somebody suggest, how to test the String.format() method?

Ihor Patsian
  • 1,288
  • 2
  • 15
  • 25
Lavanya
  • 3,903
  • 6
  • 31
  • 57

3 Answers3

1

First of all, you should not be importing android view packages into your ViewModel. So skip using things like TextUtils inside ViewModels.

As to the getApplicationContext().getString(), create an interface for this. Something like:

interface StringProvider {
    String getString(int resource);
}

Then pass that interface in your ViewModel constructor and use that to get the string you want.

When you initialize the ViewModel, you can pass a concrete implementation of StringProvider like this:

class StringProviderImpl implements StringProvider {
     String getString(int resource) {
        return SampleApp.getInstance().getApplicationContext().getString(resource);
     }
}

This way, for your unit tests, you can just mock StringProvider and don't have to worry about dealing with contexts inside your ViewModel and the related test code.

Ihor Patsian
  • 1,288
  • 2
  • 15
  • 25
Abhinav Nair
  • 958
  • 1
  • 8
  • 29
1

You don't need to test the String.format method. That is not your code, and your goal should be to test your own code. But your code is using that method, so you need to test your code. This is the part you are trying to validate or mock out as I understand it:

String.format(SampleApp.getInstance().getApplicationContext().getString(R.string.due), SampleApp.getInstance().getAccountManager().getBillingDueDate()) 

which makes several calls to SampleApp to get an instance. Since those calls to SampleApp.getInstance are static method calls, you won't be able to mock them out. There isn't enough code posted to know what SampleApp is or what SampleApp.getInstance() returns or to know if any of the subsequent calls on that instance are returning null, but one of them is. So I think to solve this you need to look at the what the getInstance method returns. If you can't touch that code and you're hoping to only modify your test classes, you may not be able to test this with mockito due to the static method.

But otherwise you will need to build a way for your tests so the call to SampleApp.getInstance returns a mock object as the instance instead of whatever real instance I presume it is returning now. Then you can mock out the subsequent methods like getApplicationContext and getString to make them return canned responses so that the string.format call will not fail on a null input.

One note of caution--if you do end up making the static getInstance method return a mock, but sure you have proper cleanup when your test is done to set it back to what it was returning originally so you don't inadvertently modify something that might cause another unrelated unit test to fail. That is always a risk if you change something returned by a static method in a unit test since you are effectively changing it for all tests.

Shawn
  • 8,374
  • 5
  • 37
  • 60
0

Considering that the test fails after the AccountManager was already used, you should have set up the SampleApp as a mock or fake already.

SampleApp app = SampleApp.getInstance()
AccountManager am = app.getAccountManager();
Context context = app.getApplicationContext();
billDate.set(!TextUtils.isEmpty(am.getDueDate()) ?
    String.format(context.getString(R.string.due), am.getBillingDueDate()) :
    context.getString(R.string.missing_due_date);

Now you only need to make sure to mock the Context you provide with with app.getApplicationContext() or the SampleApp itself, if you use app.getString() directly.

doReturn(dueFormatString).when(context).getString(R.string.due);
doReturn(dueMissingString).when(context).getString(R.string.missing_due_date);

But in general you should abstract the Context away. Not using it will simplify your code and therefore your testing a lot.

Also consider using context.getString() instead of String.format() for formatting a string you load from a resource. It's as easy as adding the format arguments as parameters to the call.

context.getString(R.string.due, am.getBillingDueDate())
tynn
  • 38,113
  • 8
  • 108
  • 143