Two equivalent MockUp ways lead to different results #313

Closed
dany52 opened this Issue Jul 20, 2016 · 4 comments

Projects

None yet

2 participants

@dany52
dany52 commented Jul 20, 2016 edited

jmockit 1.8, jmockit 1.25

I don't understand why test_2 fails when test_1 passes!?

import static org.junit.Assert.*;
import mockit.Mock;
import mockit.MockUp;

import org.junit.Before;
import org.junit.Test;

class Article {

  public String getContent() {
    return "default content";
  }

  public String getHeader() {
    return "default header";
  }
}

public class ArticleTest {

  private Article article;

  @Before
  public void init() {
    article = new Article();
  }

  @Test
  public void test_1() {

    new MockUp<Article>() {
      @Mock
      String getHeader() {
        return "header";
      }
    };

    new MockUp<Article>() {
      @Mock
      String getContent() {
        return "content1";
      }
    };
    assertEquals("content1", article.getContent());

    new MockUp<Article>() {
      @Mock
      String getContent() {
        return "content2";
      }
    };
    assertEquals("content2", article.getContent());

    assertEquals("header", article.getHeader());
  }

  @Test
  public void test_2() {
    new MockUp<Article>() {
      @Mock
      String getHeader() {
        return "header";
      }
    };

    Content.when("content1");
    assertEquals("content1", article.getContent());

    Content.when("content2");
    assertEquals("content2", article.getContent());

    assertEquals("header", article.getHeader()); //fail!
  }

}

class Content {

  public static void when(final String value) {
    new MockUp<Article>() {
      @Mock
      String getContent() {
        return value;
      }
    };
  }
}
@rliesenfeld rliesenfeld self-assigned this Jul 21, 2016
@rliesenfeld
Member

The two tests differ in which test_1 applies the same mock-up twice, while test_2 applies three different mock-ups (each one only once).

The behavior when two different mock-ups are applied to the same class is to "merge" the two mock-ups, with the second one writing over the first one.

When the same mock-up class is applied twice, however, the target class is restored and then the mock-up is re-applied; previous mock-ups on the same target class are therefore undone, even if they are different mock-ups.

This said, I think we can improve the way that re-application of the same mock-up class gets handled. Currently, it can work as expected in some cases while not in others.

Thanks for reporting the issue!

@dany52
dany52 commented Jul 21, 2016

Thanks for the quick response!

I found a similar issue
http://stackoverflow.com/questions/18227448/jmockit-how-to-override-already-mocked-method-with-a-new-mock

I rewrote the code slightly, using the idea of changing mock behavior instead of recreating mock

import static org.junit.Assert.*;
import lombok.Getter;
import lombok.Setter;
import mockit.Mock;
import mockit.MockUp;

import org.junit.Before;
import org.junit.Test;

class Article {

  public String getContent() {
    return "default content";
  }

  public String getHeader() {
    return "default header";
  }
}

public class ArticleTest {

  private Article article;

  @Before
  public void init() {
    article = new Article();
  }

  @Test
  public void test_1() {

    new MockUp<Article>() {
      @Mock
      String getHeader() {
        return "header";
      }
    };

    new MockUp<Article>() {
      @Mock
      String getContent() {
        return "content1";
      }
    };
    assertEquals("content1", article.getContent());

    new MockUp<Article>() {
      @Mock
      String getContent() {
        return "content2";
      }
    };
    assertEquals("content2", article.getContent());

    assertEquals("header", article.getHeader());
  }

  @Test
  public void test_2() {
    Mocker mocker = new Mocker();

    mocker.whenHeader("header");

    mocker.whenContent("content1");
    assertEquals("content1", article.getContent());

    mocker.whenContent("content2");
    assertEquals("content2", article.getContent());

    assertEquals("header", article.getHeader());
  }

}

class Mocker {

  protected ReusableValueMock<String, Article> header;

  protected ReusableValueMock<String, Article> content;

  public Mocker() {

    header = new ReusableValueMock<String, Article>() {

      @Override
      public MockUp<Article> create() {
        return new MockUp<Article>() {
          @Mock
          String getHeader() {
            return getValue();
          }
        };
      }
    };

    content = new ReusableValueMock<String, Article>() {

      @Override
      public MockUp<Article> create() {
        return new MockUp<Article>() {
          @Mock
          String getContent() {
            return getValue();
          }
        };
      }
    };
  }

  public void whenContent(String value) {
    content.change(value);
  }

  public void whenHeader(String value) {
    header.change(value);
  }
}

abstract class ReusableValueMock<V, T> {
  protected @Getter @Setter MockUp<T> mock;
  protected @Getter @Setter V value;

  public void change(V value) {
    if (mock == null) {
      mock = create();
    }

    this.value = value;
  }

  public abstract MockUp<T> create();
}

Now test_2 pass :)

But it would be good to have a more robust simple way for this scenario in the future releases

@rliesenfeld
Member

Which scenario? Personally, I would never use the MockUp API in the ways shown above...

BTW, I noticed all mock-ups above are anonymous and contain a single @Mock method. Just to be sure, note that mock-up classes can be named, and can contain multiple @Mock methods.

@dany52
dany52 commented Jul 21, 2016 edited

I try test something like

  • when stateA=a1 and stateB=b1 should result=r1
  • when stateA=a1 and stateB=b2 should result=r2
  • when stateA=a2 and stateB=b1 should result=r1 and etc.

I can split the big test into many small tests. But more natural combine state cases by variations.

@rliesenfeld rliesenfeld added a commit that closed this issue Jul 24, 2016
@rliesenfeld rliesenfeld Improved the way that re-application of the same mock-up class was ha…
…ndled, throwing a helpful exception instead of restoring the faked class and then re-applying the mock-up (which differed from the result of applying two different mock-up classes to the same target class, therefore confusing the user); closes #313.
00e2caa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment