Mockito lenient() when to use
For example, it's very useful while migrating from Mockito 1 to Mockito 2 (the latter introduced strict stubbing), if you need to do it in a short period of time.
SimpleDateFormat leniency leads to unexpected behavior
Why does this (not) happen?
It’s not very well explained in the documentation.
With lenient parsing, the parser may use heuristics to interpret
inputs that do not precisely match this object's format. With strict
parsing, inputs must match this object's format.
The documentation does help a bit, though, by mentioning that it is the Calendar
object that the DateFormat
uses that is lenient. That Calendar
object is not used for the parsing itself, but for interpreting the parsed values into a date and time (I am quoting DateFormat
documentation since SimpleDateFormat
is a subclass of DateFormat
).
SimpleDateFormat
, no matter if lenient or not, will accept 3-digit year, for example199
, even though you have specifiedyyyy
in the format pattern string. The documentation says about year:For parsing, if the number of pattern letters is more than 2, the year
is interpreted literally, regardless of the number of digits. So using
the pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.DateFormat
, no matter if lenient or not, accepts and ignores text after the parsed text, like the small lettero
in your first example. It objects to unexpected text before or inside the text, as when in your last example you put the lettero
in front. The documentation ofDateFormat.parse
says:The method may not use the entire text of the given string.
As I indirectly said, leniency makes a difference when interpreting the parsed values into a date and time. So a lenient
SimpleDateFormat
will interpret 29.02.2019 as 01.03.2019 because there are only 28 days in February 2019. A strictSimpleDateFormat
will refuse to do that and will throw an exception. The default lenient behaviour can lead to very surprising and downright inexplicable results. As a simple example, giving the day, month and year in the wrong order:1990.03.12
will result in August 11 year 17 AD (2001 years ago).
The solution
VGR already in a comment mentioned LocalDate
from java.time
, the modern Java date and time API. In my experience java.time
is so much nicer to work with than the old date and time classes, so let’s give it a shot. Try a correct date string first:
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.mm.yyyy");
System.out.println(LocalDate.parse("03.12.1990", dateFormatter));
We get:
java.time.format.DateTimeParseException: Text '03.12.1990' could not
be parsed: Unable to obtain LocalDate from TemporalAccessor:
{Year=1990, DayOfMonth=3, MinuteOfHour=12},ISO of type
java.time.format.Parsed
This is because I used your format pattern string of dd.mm.yyyy
, where lowercase mm
means minute. When we read the error message closely enough, it does state that the DateTimeFormatter
interpreted 12 as minute of hour, which was not what we intended. While SimpleDateFormat
tacitly accepted this (even when strict), java.time
is more helpful in pointing out our mistake. What the message only indirectly says is that it is missing a month value. We need to use uppercase MM
for month. At the same time I am trying your date string with the typo:
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
System.out.println(LocalDate.parse("03.12.199o", dateFormatter));
We get:
java.time.format.DateTimeParseException: Text '03.12.199o' could not
be parsed at index 6
Index 6 is where is says 199
. It objects because we had specified 4 digits and are only supplying 3. The docs say:
The count of letters determines the minimum field width …
It would also object to unparsed text after the date. In short it seems to me that it gives you everything that you had expected.
Links
DateFormat.setLenient
documentation- Oracle tutorial: Date Time explaining how to use
java.time
.
Lenient string matching with Mockito Mock
@Mock(lenient = true) was introduced in Mockito
version 2.23.3
.
You either have to upgrade or use the other way
to write this:
lenient().when(specialtyRepository.save(argThat(argument -> argument.getDescription().equals(MATCH_ME)))).willReturn(savedSpeciality);
DateTimeFormatter - Strict vs Lenient unexpected behaviour
Having posted this as a bug - https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8228353
I got the following reply:
According to DateTimeFormatterBuilder's spec, appendPattern("yy") translates to appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2000), where "2" applies both for with and maxWidth. In the method description, it reads:
For strict parsing, the number of characters allowed by width to maxWidth are parsed. For lenient parsing, the number of characters must be at least 1 and less than 10.
Thus in this case, only "11" is read for "yy" in strict mode, then produces the year "2011" with the base value 2000. But in lenient mode, "yy" tries to read greedily just before the ":" and produces the year "1104", then parser throws the exception trying to parse ":" with "HH" pattern.
SimpleDateFormat Exception on setting lenient to false
Because that time does not exist in your default time zone—it was daylight savings time change day, and time jumped from 2:00 a.m. to 3:00 a.m., so there was no 2:46 that morning. :P
Since you’re parsing UTC, set the SimpleDateFormat
instance time zone to UTC like so:
f.setTimeZone(TimeZone.getTimeZone("UTC"));
and your problem will go away.
Use Gson RuntimeTypeAdapterFactory with lenient = true setting
It is not quite a bug from gson-extras
since com.google.gson.TypeAdapter
class comes from core library. I would suggest to register one more TypeAdapterFactory
and force lenient
for all JsonReader
instances:
class AlwaysLenientTypeAdapterFactory implements TypeAdapterFactory {
public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return createCustomTypeAdapter(delegate);
}
private <T> TypeAdapter<T> createCustomTypeAdapter(TypeAdapter<T> delegate) {
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
@Override
public T read(JsonReader in) throws IOException {
in.setLenient(true);
return delegate.read(in);
}
};
}
}
Usage:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(adapterFactory)
.registerTypeAdapterFactory(new AlwaysLenientTypeAdapterFactory())
.create();
See also:
- Is there an easy way to make Gson skip a field if there's an error
deserializing it?
Related Topics
Adding Up Bigdecimals Using Streams
How to Print Line Numbers to the Log in Java
Timer & Timertask Versus Thread + Sleep in Java
Differencebetween Class.This and This in Java
Flutter Doctor --Android-Licenses Gives a Java Error
Why Can't I Declare Static Methods in an Interface
A Regex to Match a Substring That Isn't Followed by a Certain Other Substring
Handling Soft-Deletes with Spring JPA
Synchronization of Non-Final Field
How to Get the File Name from a String Containing the Absolute File Path
In Java, Is It More Efficient to Use Byte or Short Instead of Int and Float Instead of Double
Spring Boot Multiple Datasource
How to Disable Jsessionid in Tomcat Servlet
How to Change an Eclipse Default Project into a Java Project
How to Combine Two Hashmap Objects Containing the Same Types
Case Insensitive String as Hashmap Key
Java Arraylist - How to Tell If Two Lists Are Equal, Order Not Mattering