How to mock a String using mockito?
Asked Answered
E

16

44

I need to simulate a test scenario in which I call the getBytes() method of a String object and I get an UnsupportedEncodingException.

I have tried to achieve that using the following code:

String nonEncodedString = mock(String.class);
when(nonEncodedString.getBytes(anyString())).thenThrow(new UnsupportedEncodingException("Parsing error."));

The problem is that when I run my test case I get a MockitoException that says that I can't mock a java.lang.String class.

Is there a way to mock a String object using mockito or, alternatively, a way to make my String object throw an UnsupportedEncodingException when I call the getBytes method?


Here are more details to illustrate the problem:

This is the class that I want to test:

public final class A {
    public static String f(String str){
        try {
            return new String(str.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // This is the catch block that I want to exercise.
            ...
        }
    }
}

This is my testing class (I'm using JUnit 4 and mockito):

public class TestA {

    @Test(expected=UnsupportedEncodingException.class)
    public void test(){
        String aString = mock(String.class);
        when(nonEncodedString.getBytes(anyString())).thenThrow(new UnsupportedEncodingException("Parsing error."));
        A.f(aString);
    }
}
Endocentric answered 3/7, 2009 at 12:55 Comment(3)
It is a project requirement that the unit tests coverage percentage must but higher than a given value. To achieve such percentage of coverage the tests must cover the catch block relative to the UnsupportedEncodingException.Endocentric
Time to question those project requirements if you ask me. Test coverage should be used intelligently, just as all metrics should.Nyx
+1 to cancel duffymo's downvote. just because you don't see a reason to do this doesn't mean the OP doesn't have a reason.Bosson
F
48

The problem is the String class in Java is marked as final, so you cannot mock is using traditional mocking frameworks. According to the Mockito FAQ, this is a limitation of that framework as well.

Fibered answered 3/7, 2009 at 13:1 Comment(1)
With Mockito 2 you can mock final classes.Hotheaded
O
13

How about just creating a String with a bad encoding name? See

public String(byte bytes[], int offset, int length, String charsetName)

Mocking String is almost certainly a bad idea.

Outlandish answered 5/9, 2009 at 20:31 Comment(0)
P
13

If all you are going to do in your catch block is throw a runtime exception then you can save yourself some typing by just using a Charset object to specify your character set name.

public final class A{
    public static String f(String str){
        return new String(str.getBytes(Charset.forName("UTF-8")));
    }
}

This way you aren't catching an exception that will never happen just because the compiler tells you to.

Poling answered 21/5, 2010 at 22:43 Comment(1)
+1 Definitely the best answer (except when using a JDK older than 1.6 - but that's unlikely in 2016 - as getBytes(Charset) was only added in Java 6).Adinaadine
F
7

As others have indicated, you can't use Mockito to mock a final class. However, the more important point is that the test isn't especially useful because it's just demonstrating that String.getBytes() can throw an exception, which it can obviously do. If you feel strongly about testing this functionality, I guess you could add a parameter for the encoding to f() and send a bad value into the test.

Also, you are causing the same problem for the caller of A.f() because A is final and f() is static.

This article might be useful in convincing your coworkers to be less dogmatic about 100% code coverage: How to fail with 100% test coverage.

Fishback answered 3/12, 2009 at 2:32 Comment(0)
A
6

From its documentation, JDave can't remove "final" modifiers from classes loaded by the bootstrap classloader. That includes all JRE classes (from java.lang, java.util, etc.).

A tool that does let you mock anything is JMockit.

With JMockit, your test can be written as:

import java.io.*;
import org.junit.*;
import mockit.*;

public final class ATest
{
   @Test(expected = UnsupportedOperationException.class)
   public void test() throws Exception
   {
      new Expectations()
      {
         @Mocked("getBytes")
         String aString;

         {
            aString.getBytes(anyString);
            result = new UnsupportedEncodingException("Parsing error.");
         }
      };

      A.f("test");
   }
}

assuming that the complete "A" class is:

import java.io.*;

public final class A
{
   public static String f(String str)
   {
      try {
         return new String(str.getBytes("UTF-8"));
      }
      catch (UnsupportedEncodingException e) {
         throw new UnsupportedOperationException(e);
      }
   }
}

I actually executed this test in my machine. (Notice I wrapped the original checked exception in a runtime exception.)

I used partial mocking through @Mocked("getBytes") to prevent JMockit from mocking everything in the java.lang.String class (just imagine what that could cause).

Now, this test really is unnecessary, because "UTF-8" is a standard charset, required to be supported in all JREs. Therefore, in a production environment the catch block will never be executed.

The "need" or desire to cover the catch block is still valid, though. So, how to get rid of the test without reducing the coverage percentage? Here is my idea: insert a line with assert false; as the first statement inside the catch block, and have the Code Coverage tool ignore the whole catch block when reporting coverage measures. This is one of my "TODO items" for JMockit Coverage. 8^)

Ashely answered 29/7, 2009 at 22:36 Comment(0)
C
3

Mockito can't mock final classes. JMock, combined with a library from JDave can. Here are instructions.

JMock doesn't do anything special for final classes other than rely on the JDave library to unfinalize everything in the JVM, so you could experiment with using JDave's unfinalizer and see if Mockito will then mock it.

Catalogue answered 3/7, 2009 at 13:1 Comment(0)
H
3

You can also use PowerMock's Mockito extension to mock final classes/methods even in system classes such as String. However I would also advice against mocking getBytes in this case and rather try to setup your expectation so that a real is String populated with the expected data is used instead.

Hospitalet answered 21/12, 2009 at 9:52 Comment(1)
PowerMock wasn't able to mock String.Guard
P
3

You will be testing code that can never be executed. UTF-8 support is required to be in every Java VM, see http://java.sun.com/javase/6/docs/api/java/nio/charset/Charset.html

Pious answered 21/12, 2009 at 9:57 Comment(0)
L
1

It is a project requirement that the unit tests coverage percentage must but higher than a given value. To achieve such percentage of coverage the tests must cover the catch block relative to the UnsupportedEncodingException.

What is that given coverage target? Some people would say that shooting for 100% coverage isn't always a good idea.

Besides, that's no way to test whether or not a catch block was exercised. The right way is to write a method that causes the exception to be thrown and make observation of the exception being thrown the success criterion. You do this with JUnit's @Test annotation by adding the "expected" value:

@Test(expected=IndexOutOfBoundsException.class) public void outOfBounds() {
   new ArrayList<Object>().get(1);
}
Lethargy answered 3/7, 2009 at 15:10 Comment(2)
The code coverage for this project is 95%. I'm not the one that made the decision... :-) And you can check wether a catch block was exercised using JUnit + EMMA.Endocentric
Yes, but if you write that JUnit test to show that the exception is thrown under the proper conditions you'll have a better test suite regardless of what Emma tells you. The point is good tests before coverage.Lethargy
N
1

Have you tried passing an invalid charsetName to getBytes(String)?

You could implement a helper method to get the charsetName, and override that method within your test to a nonsense value.

Nuclease answered 3/7, 2009 at 15:12 Comment(1)
The problem is that I would have to change the class I'm testing... but that may be a solution. I could create a setter for an attribute used to select the charset, just for testing purposes.Endocentric
O
1

Perhaps A.f(String) should be A.f(CharSequence) instead. You can mock a CharSequence.

Olio answered 1/8, 2010 at 1:33 Comment(1)
CharSequence does not have getBytes. You would need to mock toString() of CharSequence to return a mock string to mock getBytes(Charset) on that mock String. And the original problem comes back.Witness
I
1

You cannot mock String with Mockito. It has nothing to do with it being final, as Mockito has been able to mock final classes since 2.0, and natively since 5.0.

If you attempt it with the latest version, you get this MockitoException:

Cannot mock/spy class java.lang.String
Mockito cannot mock/spy because :
- Cannot mock wrapper types, String.class or Class.class

This is by design.

This is working as intended. You can't mock strings. Use a real string instead.

Incompressible answered 19/3 at 14:14 Comment(0)
W
0

If you can use JMockit, look at Rogério answer.

If and only if your goal is to get code coverage but not actually simulate what missing UTF-8 would look like at runtime you can do the following (and that you can't or don't want to use JMockit):

public static String f(String str){
    return f(str, "UTF-8");
}

// package private for example
static String f(String str, String charsetName){
    try {
        return new String(str.getBytes(charsetName));
    } catch (UnsupportedEncodingException e) {
        throw new IllegalArgumentException("Unsupported encoding: " + charsetName, e);
    }
}

public class TestA {

    @Test(expected=IllegalArgumentException.class)
    public void testInvalid(){
        A.f(str, "This is not the encoding you are looking for!");
    }

    @Test
    public void testNormal(){
        // TODO do the normal tests with the method taking only 1 parameter
    }
}
Witness answered 3/2, 2017 at 3:12 Comment(0)
K
0

You can change your method to take the interface CharSequence:

public final class A {
    public static String f(CharSequence str){
        try {
            return new String(str.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // This is the catch block that I want to exercise.
            ...
        }
    }
}

That way, you can still pass in String, but you can mock any way you like.

Kroeger answered 12/1, 2018 at 5:37 Comment(0)
H
0

instead of mocking string inject string value at setup method as below

public void setup() throws IllegalAccessException {
    FieldUtils.writeField(yourTestClass, "stringVariableName", "value", true);
}
Highwrought answered 2/2, 2021 at 21:14 Comment(0)
M
-1

If you have a block of code that can never actually be run, and a managerial requirement to have 100% test coverage, then something's going to have to change.

What you could do is make the character encoding a member variable, and add a package-private constructor to your class that lets you pass it in. In your unit test, you could call the new constructor, with a nonsense value for the character encoding.

Milla answered 17/12, 2011 at 6:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.