Caiz22
Caiz22

Reputation: 817

NullPointerException from Toast while Unit Testing

I am testing a function of my Signup class, however every time I run it a NullPointerException occurs inside the first if statement it enters at:

pass.show()

I suspect it is has something to do with not testing the app with an emulator. How can I prevent errors of creating a Toast when I am testing this?

Function in Signup Class:

public boolean checkInput(String username, String email, String password, String confirmpassword) {
    if (!email.contains("@") || !email.contains(".com")) {
        Toast pass = Toast.makeText(Signup.this, "Email must be valid!", Toast.LENGTH_SHORT);
        pass.show();
        return false;
    }
    if (!password.equals(confirmpassword)) {
        Toast pass = Toast.makeText(Signup.this, "Passwords don't match!", Toast.LENGTH_SHORT);
        pass.show();
        return false;
    }
    if (!(password.length() >= 6)) {
        Toast pass = Toast.makeText(Signup.this, "Password must be at least 6 characters", Toast.LENGTH_SHORT);
        pass.show();
        return false;
    }
    else {
        return true;
    }
}

Test method:

    @Test
    public void testInvalidEmail() {

    String testUser = "testUser";
    String testEmail = "testEmail.com"; //invalid
    String testPass = "testPass";
    String testConfirmPass = "testPass";

    assertFalse(signupClass.checkInput(testUser,testEmail,testPass,testConfirmPass));
}

Upvotes: 3

Views: 1547

Answers (3)

Elye
Elye

Reputation: 60251

You'll need to decouple Toast from your function. To do that, follow the below step, where I use your shown example and implement them exactly as per your need. Hopes this answer your inquiry, and you could able to follow through.

First create an interface for printing (toasting)

interface Printer {
    void print(String message);
}

Then make your Signup class implements the Printer decouple the Toast from your function. I believe your Signup is an Activity.

public class Signup extends AppCompatActivity implements Printer {

    // ... other codes ...

    @Override
    public void print(String message) {
        Toast pass = Toast.makeText(Signup.this, message, Toast.LENGTH_SHORT);
        pass.show();
    }
}

Then now, remove your Toast in your checkInput function, and perform the printing through the printer sent to the function

public boolean checkInput(String username, String email, String password, String confirmpassword, Printer printer) {
    if (!email.contains("@") || !email.contains(".com")) {
        printer.print("Email must be valid!");
        return false;
    }
    if (!password.equals(confirmpassword)) {
        printer.print("Passwords don't match!");
        return false;
    }
    if (!(password.length() >= 6)) {
        printer.print("Password must be at least 6 characters");
        return false;
    }
    else {
        return true;
    }
}

Note: whichever function that calls checkInput from your class would need to send in the Printer, which is actually this if it is in Signup class e.g.

checkInput(testUser, testEmail, testPass, testConfirmPass, this)l;

Now in your test, just implement the Mock Printer e.g.

@Test
public void testInvalidEmail(MainActivity mainActivity) {

    String testUser = "testUser";
    String testEmail = "testEmail.com"; //invalid
    String testPass = "testPass";
    String testConfirmPass = "testPass";

    assertFalse(signUp.checkInput(testUser, testEmail, testPass, testConfirmPass, new Printer() {
        @Override
        public void print(String message) {
            System.out.println(message);
        }
    }));
}

With that, you could have your test done as per your desire, while your App still print through Toast

Upvotes: 1

Rubick
Rubick

Reputation: 306

Instead of passing the Toast on variable 'pass', just directly type 'Toast' and click the second suggestion.the I also remove your return.

This is the problem that I've seen on your code:

Toast pass = Toast.makeText (.....);

if you want to put it on variable, do like this

Toast pass = new Toast(getContext());


pass.makeText (.....).show();

or

pass.makeText(....);
pass.show();

so my suggestion is the option to and try this:

public boolean checkInput(String username, String email, String password, String confirmpassword) {
if (!email.contains("@") || !email.contains(".com")) {
    Toast.makeText(Signup.this, "Email must be valid!", Toast.LENGTH_SHORT).show();

}
if (!password.equals(confirmpassword)) {
   Toast.makeText(Signup.this, "Passwords don't match!", Toast.LENGTH_SHORT).show();

}
if (!(password.length() >= 6)) {
    Toast pass = Toast.makeText(Signup.this, "Password must be at least 6 characters", Toast.LENGTH_SHORT).show();
   }
}

Upvotes: -1

sebasira
sebasira

Reputation: 1834

If what you want to do is UnitTesting then you need to decouple your code for the Android stuff (like Toast).

A way to go is create an interface to display a Toast (or handle any Android related component) so you can mock it.

For example:

public interface AndroidInteraction (){
    void showToast(Context context, String text, int length);
}

And implement that interface in your activity:

public class SignUpActivity extends AppCompatActivity implements AndroidInteraction{
    .......
    ......

    @Override
    void showToast(Context context, String text, int length){
         Toast pass = Toast.makeText(context, text, length);
         pass.show();
    }

And finally your Signup class should be aware of that interface, so you could pass it throw it's constructor:

AndroidInteraction androidInteraction;
public Signup (AndroidInteraction androidInteraction){
    this.androidInteraction = androidInteraction;
}

So your method now will look like (only one if clause to show how to use it, not the whole method):

if (!password.equals(confirmpassword)) {
    androidInteraction.showToast(Signup.this, "Passwords don't match!", Toast.LENGTH_SHORT);
    return false;
}

And you will need to mock that interface in your test and pass it to the class constructor

Upvotes: 1

Related Questions