I need some advice using jmockit with kotlin.
(CUT) This is my (Java) class under test:
public final class NutritionalConsultant {
public static boolean isLunchTime() {
int hour = LocalDateTime.now().getHour();
return hour >= 12 && hour <= 14;
}
}
(j.1) This is a working Java test class
#RunWith(JMockit.class)
public class NutritionalConsultantTest {
#Test
public void shouldReturnTrueFor12h(#Mocked final LocalDateTime dateTime) {
new Expectations() {{
LocalDateTime.now(); result = dateTime;
dateTime.getHour(); result = 12;
}};
boolean isLunchTime = NutritionalConsultant.isLunchTime();
assertThat(isLunchTime, is(true));
}
}
(kt.1) However, the corresponding kotlin class throws an exception
RunWith(javaClass<JMockit>())
public class NutritionalConsultantKt1Test {
Test
public fun shouldReturnTrueFor12h(Mocked dateTime : LocalDateTime) {
object : Expectations() {{
LocalDateTime.now(); result = dateTime;
dateTime.getHour(); result = 12;
}}
val isLunchTime = NutritionalConsultant.isLunchTime()
assertThat(isLunchTime, eq(true));
}
}
Exception:
java.lang.Exception: Method shouldReturnTrueFor12h should have no parameters
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:41)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
The same exception is thrown when run with gradle.
(kt.2) Using the #Mocked syntax with kotlin I am getting a different exception:
RunWith(javaClass<JMockit>())
public class NutritionalConsultantKt2Test {
Mocked
var dateTime : LocalDateTime by Delegates.notNull()
Test
public fun shouldReturnTrueFor12h() {
object : Expectations() {{
LocalDateTime.now(); result = dateTime;
dateTime.getHour(); result = 12;
}}
val isLunchTime = NutritionalConsultant.isLunchTime()
assertThat(isLunchTime, eq(true));
}
}
Exception:
java.lang.IllegalArgumentException: Final mock field "dateTime$delegate" must be of a class type
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
edit 20150224 maybe this is related to "For a mock field, an instance of the declared type will be automatically created by JMockit and assigned to the field, provided it's not final." (from http://jmockit.org/tutorial/BehaviorBasedTesting.html)
(kt.3) However, changing the val to var and using a !! operator leads to a working test... but this is not idiomatic kotlin code:
RunWith(javaClass<JMockit>())
public class NutritionalConsultantKt3Test {
Mocked
var dateTime : LocalDateTime? = null
Test
public fun shouldReturnTrueFor12h() {
object : Expectations() {{
LocalDateTime.now(); result = dateTime;
dateTime!!.getHour(); result = 12;
}}
val isLunchTime = NutritionalConsultant.isLunchTime()
assertThat(isLunchTime, eq(true));
}
}
Did you have more success using kotlin with jmockit?
I don't think you'll be able to use JMockit from Kotlin (or most other JVM alternative languages, with the possible exception of Groovy), not reliably anyway.
The reasons are that 1) JMockit was not developed with such languages in mind, and isn't tested with them; and 2) these languages, when compiled to bytecode, produce additional or different constructs that may confuse a tool like JMockit; also they usually insert calls to their own internal APIs which may also get in the way.
In practice, alternative languages tend to develop their own testing/mocking/etc. tools, that not only work well for that language and its runtime, but also let you take full advantage of the language's strenghts.
Personally, even though I can recognize the many benefits such languages bring (and I particularly like Kotlin), I would rather stick with Java (which continues to evolve - see Java 8). The fact is, so far no alternative JVM language has come even close of Java's widespread use, and (IMO) they never will.
We've experimented a little and found that you can define special function like this:
fun uninitialized<T>() = null as T
and then use it like this:
[Mocked] val dateTime : LocalDateTime = uninitialized()
You can also use it instead of Matchers.any() for the same effect.
We will consider adding it to compiler or standard library.
Related
I've got following setup: C#, ServiceStack, MariaDB, POCOs with objects and structs, JSON.
The main question is: how to use ServiceStack to store POCOs to MariaDB having complex types (objects and structs) blobbed as JSON and still have working de/serialization of the same POCOs? All of these single tasks are supported, but I had problems when all put together mainly because of structs.
... finally during writing this I found some solution and it may look like I answered my own question, but I still would like to know the answer from more skilled people, because the solution I found is a little bit complicated, I think. Details and two subquestions arise later in the context.
Sorry for the length and for possible misinformation caused by my limited knowledge.
Simple example. This is the final working one I ended with. At the beginning there were no SomeStruct.ToString()/Parse() methods and no JsConfig settings.
using Newtonsoft.Json;
using ServiceStack;
using ServiceStack.DataAnnotations;
using ServiceStack.OrmLite;
using ServiceStack.Text;
using System.Diagnostics;
namespace Test
{
public class MainObject
{
public int Id { get; set; }
public string StringProp { get; set; }
public SomeObject ObjectProp { get; set; }
public SomeStruct StructProp { get; set; }
}
public class SomeObject
{
public string StringProp { get; set; }
}
public struct SomeStruct
{
public string StringProp { get; set; }
public override string ToString()
{
// Unable to use .ToJson() here (ServiceStack does not serialize structs).
// Unable to use ServiceStack's JSON.stringify here because it just takes ToString() => stack overflow.
// => Therefore Newtonsoft.Json used.
var serializedStruct = JsonConvert.SerializeObject(this);
return serializedStruct;
}
public static SomeStruct Parse(string json)
{
// This method behaves differently for just deserialization or when part of Save().
// Details in the text.
// After playing with different options of altering the json input I ended with just taking what comes.
// After all it is not necessary, but maybe useful in other situations.
var structItem = JsonConvert.DeserializeObject<SomeStruct>(json);
return structItem;
}
}
internal class ServiceStackMariaDbStructTest
{
private readonly MainObject _mainObject = new MainObject
{
ObjectProp = new SomeObject { StringProp = "SomeObject's String" },
StringProp = "MainObject's String",
StructProp = new SomeStruct { StringProp = "SomeStruct's String" }
};
public ServiceStackMariaDbStructTest()
{
// This one line is needed to store complex types as blobbed JSON in MariaDB.
MySqlDialect.Provider.StringSerializer = new JsonStringSerializer();
JsConfig<SomeStruct>.RawSerializeFn = someStruct => JsonConvert.SerializeObject(someStruct);
JsConfig<SomeStruct>.RawDeserializeFn = json => JsonConvert.DeserializeObject<SomeStruct>(json);
}
public void Test_Serialization()
{
try
{
var json = _mainObject.ToJson();
if (!string.IsNullOrEmpty(json))
{
var objBack = json.FromJson<MainObject>();
}
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
public void Test_Save()
{
var cs = "ConnectionStringToMariaDB";
var dbf = new OrmLiteConnectionFactory(cs, MySqlDialect.Provider);
using var db = dbf.OpenDbConnection();
db.DropAndCreateTable<MainObject>();
try
{
db.Save(_mainObject);
var dbObject = db.SingleById<MainObject>(_mainObject.Id);
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
}
What (I think) I know / have tried but at first didn't help to solve it myself:
ServiceStack stores complex types in DB as blobbed JSV by default (last paragraph of first section: https://github.com/ServiceStack/ServiceStack.OrmLite), so it is necessary to set it the way it is proposed: MySqlDialect.Provider.StringSerializer = new JsonStringSerializer(); (https://github.com/ServiceStack/ServiceStack.OrmLite#pluggable-complex-type-serializers)=> default JSV changed to JSON.
the ServiceStack's serialization does not work with structs, it is necessary to treat them special way:
a) according to https://github.com/ServiceStack/ServiceStack.Text#c-structs-and-value-types and example https://github.com/ServiceStack/ServiceStack.Text/#using-structs-to-customize-json it is necessary to implement TStruct.ToString() and static TStruct.ParseJson()/ParseJsv() methods.
b) according to https://github.com/ServiceStack/ServiceStack.Text/#typeserializer-details-jsv-format and unit tests https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/CustomStructTests.cs it shall be TStruct.ToString() (the same as in a) and static TStruct.Parse().
Subquestion #1: which one is the right one? For me, ParseJson() was never called, Parse() was. Documentation issue or is it used in other situation?
I implemented option b). Results:
IDbConnection.Save(_mainObject) saved the item to MariaDB. Success.
Through the saving process ToString() and Parse() were called. In Parse, incoming JSON looked this way:
"{\"StringProp\":\"SomeStruct's String\"}". Fine.
Serialization worked. Success.
Deserialization failed. I don't know the reason, but JSON incoming to Parse() was "double-escaped":
"{\\\"StringProp\\\":\\\"SomeStruct's String\\\"}"
Subquestion #2: Why the "double-escaping" in Parse on deserialization?
I tried to solve structs with JsConfig (and Newtonsoft.Json to get proper JSON):
JsConfig<SomeStruct>.SerializeFn = someStruct => JsonConvert.SerializeObject(someStruct);
JsConfig<SomeStruct>.DeSerializeFn = json => JsonConvert.DeserializeObject<SomeStruct>(json);
a) at first without ToString() and Parse() defined in the TStruct. Results:
Save failed: the json input in JsonConvert.DeserializeObject(json) that is used during Save was just type name "WinAmbPrototype.SomeStruct".
De/serialization worked.
b) then I implemented ToString() also using Newtonsoft.Json. During Save ToString() was used instead of JsConfig.SerializeFn even the JsConfig.SerializeFn was still set (maybe by design, I do not judge). Results:
Save failed: but the json input of DeserializeFn called during Save changed, now it was JSV-like "{StringProp:SomeStruct's String}", but still not deserializable as JSON.
De/serialization worked.
Then (during writing this I was still without any solution) I found JsConfig.Raw* "overrides" and tried them:
JsConfig<SomeStruct>.RawSerializeFn = someStruct => JsonConvert.SerializeObject(someStruct);
JsConfig<SomeStruct>.RawDeserializeFn = json => JsonConvert.DeserializeObject<SomeStruct>(json);
a) at first without ToString() and Parse() defined in the TStruct. Results are the same as in 2a.
b) then I implemented ToString(). Results:
BOTH WORKED. No Parse() method needed for this task.
But it is very fragile setup:
if I removed ToString(), it failed (now I understand why, default ToString produced JSON with just type name in 2a, 3a).
if I removed RawSerializeFn setting, it failed in RawDeserializeFn ("double-escaped" JSON).
Is there some simpler solution? I would be very glad if someone points me to better direction.
Acceptable would be maybe two (both of them accessible because of different circumstances):
if I am the TStruct owner: with just pure TStruct.ToString() and static TStruct.Parse() to support out of the box de/serialization and DB by ServiceStack (without different input in Parse()).
if I am a consumer of TStruct with no JSON support implemented and I am without access to its code: until now I did not find the way, if the ToString is not implemented: Save to DB did not work. Maybe would be fine to ensure JsConfig serialize functions are enough for both de/serialization and when used during saving to DB.
And the best one would be without employing other dependency (e.g. Newtonsoft.Json) to serialize structs. Maybe some JsConfig.ShallProcessStructs = true; (WARNING: just a tip, not working as of 2021-04-02) would be fine for such situations.
ServiceStack treats structs like a single scalar value type, just like most of the core BCL Value Types (e.g. TimeSpan, DateTime, etc). Overloading the Parse() and ToString() methods and Struct's Constructor let you control the serialization/deserialization of custom structs.
Docs have been corrected. Structs use Parse whilst classes use ParseJson/ParseJsv
If you want to serialize a models properties I'd suggest you use a class instead as the behavior you're looking for is that of a POCO DTO.
If you want to have structs serailized as DTOs in your RDBMS an alternative you can try is to just use JSON.NET for the complex type serialization, e.g:
public class JsonNetStringSerializer : IStringSerializer
{
public To DeserializeFromString<To>(string serializedText) =>
JsonConvert.DeserializeObject<To>(serializedText);
public object DeserializeFromString(string serializedText, Type type) =>
JsonConvert.DeserializeObject(serializedText, type);
public string SerializeToString<TFrom>(TFrom from) =>
JsonConvert.SerializeObject(from);
}
MySqlDialect.Provider.StringSerializer = new JsonNetStringSerializer();
I have this TestNG test method code:
#InjectMocks
private FilmeService filmeService = new FilmeServiceImpl();
#Mock
private FilmeDAO filmeDao;
#BeforeMethod(alwaysRun=true)
public void injectDao() {
MockitoAnnotations.initMocks(this);
}
//... another tests here
#Test
public void getRandomEnqueteFilmes() {
#SuppressWarnings("unchecked")
List<Filme> listaFilmes = mock(List.class);
when(listaFilmes.get(anyInt())).thenReturn(any(Filme.class));
when(filmeDao.listAll()).thenReturn(listaFilmes);
List<Filme> filmes = filmeService.getRandomEnqueteFilmes();
assertNotNull(filmes, "Lista de filmes retornou vazia");
assertEquals(filmes.size(), 2, "Lista não retornou com 2 filmes");
}
And I'm getting a "org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
0 matchers expected, 1 recorded:" in the call of listAll() method in this code:
#Override
public List<Filme> getRandomEnqueteFilmes() {
int indice1, indice2 = 0;
List<Filme> filmesExibir = new ArrayList<Filme>();
List<Filme> filmes = dao.listAll();
Random randomGenerator = new Random();
indice1 = randomGenerator.nextInt(5);
do {
indice2 = randomGenerator.nextInt(5);
} while(indice1 == indice2);
filmesExibir.add(filmes.get(indice1));
filmesExibir.add(filmes.get(indice2));
return filmesExibir;
}
I'm prety sure I'm missing something here but I don't know what it is! Someone help?
when(listaFilmes.get(anyInt())).thenReturn(any(Filme.class));
There's your problem. You can't use any in a return value. any is a Matcher—it's used to match parameter values for stubbing and verification—and doesn't make sense in defining a return value for a call. You'll need to explicitly return a Filme instance, or leave it null (which is the default behavior, which would defeat the point of stubbing).
I should note that it's often a good idea to use a real List instead of a mock List. Unlike custom code you've developed, List implementations are well-defined and well-tested, and unlike mock Lists a real List is very unlikely to break if you refactor your system under test to call different methods. It's a matter of style and testing philosophy, but you may find it advantageous just to use a real List here.
Why would the above rule cause that exception? Well, this explanation breaks some of Mockito's abstractions, but matchers don't behave like you think they might—they record a value onto a secret ThreadLocal stack of ArgumentMatcher objects and return null or some other dummy value, and in the call to when or verify Mockito sees a non-empty stack and knows to use those Matchers in preference to actual argument values. As far as Mockito and the Java evaluation order are concerned, your code looks like the following:
when(listaFilmes.get(anyInt())).thenReturn(null);
when(filmeDao.listAll(any())).thenReturn(listaFilmes); // nonsense
Naturally Mockito sees an any matcher, and listAll doesn't take an argument, so there are 0 matchers expected, 1 recorded.
I'm using a Calendar object to determine whether or not to increase the workload of a system based on current day/hour values. Given that this object uses static methods, I'm using PowerMock to mock the static methods with the following annotations:
#RunWith(PowerMockRunner.class)
#PrepareForTest({ Calendar.class })
The code under test is pretty simple (though my logic needs work, I know):
public void determineDefaultMaximumScans() throws ParseException{
parseTime();
Calendar cal = Calendar.getInstance();
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
System.out.println(cal.get(Calendar.DAY_OF_WEEK));
if(dayOfWeek == (Calendar.SATURDAY) || dayOfWeek == (Calendar.SUNDAY)){
setDefaultMax(calculateNewDefaultMax(getDefaultMax()));
System.out.println("defaultMax increased by 20%");
} else {
if(currentTime.after(afterHoursBegin) && currentTime.before(afterHoursEnd)){
System.out.println("Not afterhours. Maintaining current maximum.");
setDefaultMax(defaultMax);
System.out.println("Current Maximum number of scans: " + getDefaultMax());
}
}
}
My test case reads as follows:
#SuppressWarnings("static-access")
#Test
public void testDetermineMaximumScans() throws ParseException{
PowerMock.mockStatic(Calendar.class);
String beginningTime = "18:00";
String endingTime = "05:00";
mockAfterHoursBegin = parser.parse(beginningTime);
mockAfterHoursEnd = parser.parse(endingTime);
mockCurrentTime = parser.parse(parser.format(new Date()));
EasyMock.expect(Calendar.getInstance()).andReturn(mockCalendar);
EasyMock.expect(mockCalendar.get(Calendar.DAY_OF_WEEK)).andReturn(6);
EasyMock.replay(mocks);
offHourMaximumCalculator.determineDefaultMaximumScans();
EasyMock.verify(mocks);
}
As of now, all of my attempts to return a specific value result in the following assertion error. Now I vaguely understand why it's returning the default but I do not see why I can't force the value or how to get around this expectation. Mocks in general are still a frustrating mystery to me. What am I missing?
java.lang.AssertionError:
Expectation failure on verify:
Calendar.get(7): expected: 1, actual: 0
Mocks are fairly simple. But wanting to mock static methods is a big running after complexity. I generally do not recommend to mock something like a Calendar. If you do weird and complex thing with it, just encapsulate in something you can test and mock easily.
And in fact, we pretty much never use Calendar.getInstance(). It returns something according to the locale. But it's rare that you don't want a specific calendar i.e. GregorianCalendar. So just do new GregorianCalendar.
But anyway, add a protected method doing
protected Calendar newCalendar() {
return Calendar.getInstance(); // or new GregorianCalendar()
}
will take 2 minutes and then a simple partial mock will do the trick.
Finally, I also don't recommend to use Calendar. You have a much nicer API in java.util.date in Java 8.
All this said, here is how you should do it. Calendar is a system class, so you need to follow a real specific path which is explained here.
#RunWith(PowerMockRunner.class)
#PrepareForTest(Calendar.class)
public class MyTest {
#Test
public void testDetermineMaximumScans() throws ParseException {
PowerMock.mockStatic(Calendar.class);
Calendar calendar = mock(Calendar.class);
EasyMock.expect(Calendar.getInstance()).andReturn(calendar);
EasyMock.expect(calendar.get(Calendar.DAY_OF_WEEK)).andReturn(6);
// really important to replayAll to replay the static expectation
PowerMock.replayAll(calendar);
assertThat(Calendar.getInstance().get(Calendar.DAY_OF_WEEK)).isEqualTo(6);
// and verifyAll is you want to verify that the static call actually happened
PowerMock.verifyAll();
}
}
Firstly, please forgive any rookie mistakes here - I'm not a regular poster I'm afraid.
Now on to the nitty gritty...
I am trying to use ServiceStack.Text to serialize objects to CSV. If I keep it simple, everything works as expected when serializing objects of a known type.
However I want to serialize many objects and I don't know the type at runtime so I am writing a reusable component where all data is treated as a System.Object. We already do this same routine for Json serialization without problems. But CsvSerializer appears to handle objects differently during serialization.
Sample code
public void TestIEnumerableObjectSerialization()
{
var data = GenerateSampleData();
JsConfig<DateTime>.SerializeFn =
time => new DateTime(time.Ticks, DateTimeKind.Utc).ToString("yyyy-MM-dd HH:mm:ss");
var csv = CsvSerializer.SerializeToCsv(data);
Console.WriteLine(csv);
Assert.Equal("DateTime\r\n"
+ "2017-06-14 00:00:00\r\n"
+ "2017-01-31 01:23:45\r\n",
csv);
}
object[] GenerateSampleData()
{
return new object[] {
new POCO
{
DateTime = new DateTime(2017,6,14)
},
new POCO
{
DateTime = new DateTime(2017,1,31, 01, 23, 45)
}
};
}
public class POCO
{
public DateTime DateTime { get; set; }
}
The result of this code is that the custom serialization function is not invoked, and the DateTime is written out using the standard ToString() method.
The cause?
The CsvWriter.Write method is inspecting the type of the records and if the type is Object it is treated as a Dictionary<string, object> and CsvDictionaryWriter generates the output.
In turn, CsvDictionaryWriter uses the ToCsvField() extension method to write each property a record.
The problem is that ToCsvField() converts the value of each property to a string using ToString() meaning no custom serialization is performed.
JsonSerializer uses TypeSerializer.SerializeToString(text) to serialize the properties of an Object using any configured custom serialization functions; but this doesn't happen with CsvSerializer.
A possible solution?
Without complicating CsvSerializer, the ToCsvField() extension method could be updated to use TypeSerializer to handle the serialization to a string. Here is what I've been testing with so far:
public static object ToCsvField(this object text)
{
var textSerialized = TypeSerializer.SerializeToString(text).StripQuotes();
return textSerialized == null || !CsvWriter.HasAnyEscapeChars(textSerialized)
? textSerialized
: string.Concat
(
CsvConfig.ItemDelimiterString,
textSerialized.Replace(CsvConfig.ItemDelimiterString, CsvConfig.EscapedItemDelimiterString),
CsvConfig.ItemDelimiterString
);
}
So far I haven't come across an issue with this change, although someone may prefer not to allocate a new intermediate variable before the return statement.
Hopefully that is enough information, so on to my questions...
Has anyone else experienced this issue?
Am I doing something wrong and should I be serializing Objects a different way?
If this is a suitable fix/implementation of TypeSerializer, what are the chances of this being addressed in an update to ServiceStack.Text? I would raise an issue on GitHub but the ServiceStack.Text repo doesn't let me raise issues.
Thanks in advance.
When I execute the following test, the original object is being returned and not the mock, so the real method getLevelCriteriaForLevel(level) is being executed (I observed that with the debugger). Why is that so? I'm pretty sure that this already worked yesterday and I didn't change anything there.
I already tried
#PrepareForTest({LevelCriteria.class, LevelGenerator.class})
or used the MockitoJunitRunner as I did before, but this doesn't help either.
Here is the code (generateConcreteLevel is a private method. The expected exception is only thrown when I pass this data from the mock. Otherwise it's not thrown. The test fails because the exception is not thrown, because the test doesn't use the mock object but the real object):
public class LevelGenerator
{
public void createLevel(int level)
{
generateConcreteLevel(levelCriteria.getLevelCriteriaForLevel(level));
}
private void generateConcreteLevel(LevelCriterion levelCriterion)
{
int entryGroupCount = levelCriterion.getEntryGroupCount();
int exitGroupCount = levelCriterion.getExitGroupCount();
int exitsWhileEntries = levelCriterion.getExitsWhileEntries();
int maxGroupSize = levelCriterion.getMaxGroupSize();
List<Question> questions = levelCriterion.getQuestions();
int speed = levelCriterion.getSpeed();
Range blueItemsCount = levelCriterion.getBlueItemsCount();
Range brownItemsCount = levelCriterion.getBrownItemsCount();
Range greenItemsCount = levelCriterion.getGreenItemsCount();
Range redItemsCount = levelCriterion.getRedItemsCount();
Range whiteItemsCount = levelCriterion.getWhiteItemsCount();
Range timespanBetweenGroups = levelCriterion.getTimespanBetweenGroups();
float fractionOfCarAmountToLeave = levelCriterion.getFractionOfCarAmountToLeave();
float fractionOfMinimumItemsAmountInCarParkToStartExits = levelCriterion.getFractionOfMinimumItemsAmountInCarParkToStartExits();
checkItemsFitInEntryGroups(entryGroupCount, maxGroupSize, blueItemsCount, brownItemsCount, greenItemsCount, redItemsCount, whiteItemsCount);
}
private void checkItemsFitInEntryGroups(int entryGroupCount, int maxGroupSize, Range... ranges)
{
int totalRangeCount = 0;
for (Range range : ranges)
{
totalRangeCount += range.getMaximum();
}
if (totalRangeCount > entryGroupCount * maxGroupSize)
{
throw new UnsupportedOperationException("Error in level criterion: Not enough entry groups for Items.");
}
}
}
and the test...
#RunWith(PowerMockRunner.class)
public class LevelGeneratorTest
{
#Mock
private LevelCriteria levelCriteriaMock;
#InjectMocks
private LevelGenerator levelGenerator;
#Test(expected=UnsupportedOperationException.class)
public void tooLessPositionsInEntryGroups()
{
LevelCriterion levelCriterion = new LevelCriterion.LevelCriterionBuilder()
.withBlueItemsCount(new Range(10, 10))
.withEntryGroupCount(3)
.withExitGroupCount(3)
.withMaxGroupSize(3)
.build();
when(levelCriteriaMock.getLevelCriteriaForLevel(anyInt())).thenReturn(levelCriterion);
levelGenerator.createLevel(0);
}
}
By the way: It's not an Eclipse problem, since a Maven build produces the same error.
I know that I can execute the private method with PowerMockito's Whitebox directly what I will do after refactoring, but the question is, why when...then is not working here.
Assuming your question is:
Why is the real method getLevelCriteriaForLevel(level) executed when I expect to have the mocked implementation?
I will try to demonstrate that everything works as expected fine if you reproduce it is a simple example (switch from your current workspace if you need to).
I do not have you LevelCriterion.LevelCriterionBuilder() so I simplified your test by just instantiating a LevelCriterion (empty object). This is not relevant because in my implementation I will always return an UnsupportedOperationException.
Here is how my LevelGenerator looks like. The UnsupportedOperationException is as simple as possible LevelGenerator#generateConcreteLevel(LevelCriterion).
If implemented like this:
public class LevelGenerator
{
LevelCriteria levelCriteria;
public void createLevel(int level)
{
generateConcreteLevel(levelCriteria.getLevelCriteriaForLevel(level));
}
private void generateConcreteLevel(LevelCriterion levelCriteriaForLevel)
{
throw new UnsupportedOperationException("xxx");
}
}
The test is green, using a #RunWith(MockitoJUnitRunner.class) on top of LevelGeneratorTest.
I did not used #PrepareForTest or PowerMockRunner. I have Mockito version 1.9.5.
I am sure that I have not used the real getLevelCriteriaForLevel(level) implementation, because it looks like this:
public class LevelCriteria {
public LevelCriterion getLevelCriteriaForLevel(int level) {
throw new IllegalStateException("Unexpected call");
}
}
If the real getLevelCriteriaForLevel(int) is excuted the test is red (because an IllegalStateException is thrown).
And the test is still green...
This works with pure-Mockito (version 1.9.5) approach.
You should remove all other mocking library (PowerMock, EasyMock...)
Are you sure that your static call of when(..).thenReturn(..) is the Mockito one?
Have you tried: Mockito.when(..).thenReturn(..)
I hope it helps.
PS: I have seen that you updated the question. It is a little bit unfair for me, but I am not going to update my answer once again.
I do not need the implementation detail of generateConcreteLevel(..) to illustrate that the when(..) call is working. Read my answer again. Try it in a separate workspace and you will see by yourself.
PS-2: I am using Eclipse too... Why should it be a problem? Eclipse is a great IDE.
I figured out what's wrong. Unfortunately you don't see the real problem in this question since I simplified the classes:
My real implementation of LevelGenerator got a parameterized constructor today. That's the reason why it worked yesterday but not anymore. Obviously there are mock injection problems with Mockito when not using a standard constructor.
Result: The when...then works as excpected.