New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two equivalent MockUp ways lead to different results #313

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

Comments

2 participants
@dany52

dany52 commented Jul 20, 2016

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

This comment has been minimized.

Show comment
Hide comment
@rliesenfeld

rliesenfeld Jul 21, 2016

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!

Member

rliesenfeld commented Jul 21, 2016

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

This comment has been minimized.

Show comment
Hide comment
@dany52

dany52 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

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

This comment has been minimized.

Show comment
Hide comment
@rliesenfeld

rliesenfeld Jul 21, 2016

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.

Member

rliesenfeld commented Jul 21, 2016

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

This comment has been minimized.

Show comment
Hide comment
@dany52

dany52 Jul 21, 2016

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.

dany52 commented Jul 21, 2016

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment