Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock UUID.randomUUID() from java.util package?

What is wrong with the UUID.randomUUID() - it just can't be mocked

Is it possible to mock? Or i have an error in my source?

Look at example:

1) Class that is tested

package com.grayen;

import java.util.UUID;

public class TestedClass {
    public UUID getUuid() {
        return UUID.randomUUID();
    }
    public UUID getUuidFromWrapper() {
        return UuidWrapper.randomUUID();
    }
}

One method uses a wrapper for UUID and i can mock that wrapper!

2) Wrapper for real UUID (all modifiers the same)

package com.grayen;

import java.util.UUID;

public final class UuidWrapper {
    public static UUID randomUUID() {
        return UUID.randomUUID();
    }
}

3) Testing (last commented line throws an exception)

package com.grayen;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.util.UUID;
import static org.junit.Assert.assertEquals;

@PrepareForTest({UUID.class, UuidWrapper.class})
@RunWith(PowerMockRunner.class)
public class TestedClassTest {

    @Test
    public void testMethod() {
        UUID uuid = UUID.randomUUID();

        PowerMockito.mockStatic(UUID.class);
        PowerMockito.mockStatic(UuidWrapper.class);

        PowerMockito.when(UUID.randomUUID()).thenReturn(uuid);
        PowerMockito.when(UuidWrapper.randomUUID()).thenReturn(uuid);

        TestedClass testedClass = new TestedClass();

        assertEquals(uuid, testedClass.getUuidFromWrapper());
        //assertEquals(uuid, testedClass.getUuid());
    }
}
like image 990
Maksim Petrov Avatar asked Sep 06 '18 17:09

Maksim Petrov


1 Answers

Mocking a static method is always a fragile approach. If possible, prefer to use a non-static UUID source, which then can be easily mocked.

For example:

/**
 * A source of new {@link UUID} instances.
 */
public interface UuidSource {
    /**
     * Returns a new {@link UuidSource} that generates UUIDs using {@link UUID#randomUUID}.
     */
    public static UuidSource random() {
        return UUID::randomUUID;
    }

    /**
     * Returns a new {@link UUID} instance.
     *
     * <p>The returned value is guaranteed to be unique.
     */
    UUID newUuid();
}

Then you can inject it into TestedClass, or have TestedClass have a private member:

public class TestedClass {
    private UuidSource uuidSource = UuidSource.random();

    public UUID getUUID() {
        return uuidSource.newUuid();
    }
    // etc.
}

Then to test it, you can either have a test-only constructor, to allow injecting in a mocked UuidSource, or you could replace the value of the uuidSource field directly (either by widening its visibility or by using reflection or something).

As a bonus: this decouples your actual production code from UUID.randomUUID(). If later you decide that you need to use version 2 UUIDs (datetime-based) or some other version rather than random UUIDs, you can easily change that in your production code as well. This is what people mean when they say that making your code more testable usually leads to a better overall design.

like image 127
Daniel Pryden Avatar answered Oct 26 '22 00:10

Daniel Pryden