I am having difficulty in getting a test to work using Spock Framework in a Java project. I have the following:
- Person.class
- Attribute.class
I have a service class which is being mocked in my test. The method I am trying to mock has the following signature:
serviceClass.call(Map<Person, List<Attribute>> map)
When I mock it purely using wildcards like:
serviceClass.call(_) >> MockReturnObject
Everything works as expected. The call in the service class will return the MockReturnObject.
However, for my specific case I need to specify the Person object I am passing in and assign it a specific MockReturnObject. Like:
serviceClass.call([(PersonA):_)]) >> MockReturnObjectA
or
def listWildcard = _
serviceClass.call(Map.of(PersonA, listWildcard)) >> MockReturnObjectA
Neither of these approaches work and the call ends up returning null instead of MockReturnObjectA (I assume it is because it is failing to match the arguments). I am unfortunately not too experienced with Spock and my attempt to search through documentation on handling Maps in this scenario has not come up fruitful. I would appreciate anyone able to offer any guidance.
I don't think it makes a difference but PersonA is passed in to an entry method in the serviceClass in a list like;
List<Attribute> list = getAttributeList()
entryClass.process(List<Person> personList) {
personList.forEach(person -> serviceClass.call(Map.of(person, list))
}
So in my tests, the "when" is:
entryClass.process([PersonA, PersonB, PersonC])
With all 3 being Mock(Person.class) with their own behaviours.
When you use an object as parameter Spock will use Groovy equality to compare the argument. However, if it is too complicated to construct the reference object, you can use code argument constraints instead to programmatically check the actual argument.
serviceClass.call({ it[PersonA] == attributeList }) >> MockReturnObject
As you've shared very little code this is the best example I can give.
Related
I'm still a basic learner for Unit Test.
I'm trying to mock my class's method without directly calling it(since it is dependent on 3rd party library) and I wrote a test method like below.
(The 3rd party library which I mentioned is not MapStruct, it is ModelObject.class and it has very complicated parameters for the constructor and is only available to initialize in library level)
The ProjectMapper class's toDto method is a simple object mapping method(with MapStruct library).
#Test
#DisplayName("Should convert Project ModelObject to Project DTO successfully.")
void testProjectModelObjectToDtoSucess() {
// Given
ModelObject mockModelObject = mock(ModelObject.class); // <-- Mocking "ModelObject.class"
ProjectDto expected = new ProjectDto("PPJT-00000001", "Test Project 01"); // <-- Initializing expected return object.
when(mockProjectMapper.toDto(mockModelObject)).thenReturn(expected); // <-- Mocking cut(class under test)'s method.
// When
ProjectDto actual = mockProjectMapper.toDto(mockModelObject); // <-- Calling mocked method.
// Then
assertEquals(actual.getId(), expected.getId(), "The converted DTO's ID should equal to expected DTO's ID.");
assertEquals(actual.getName(), expected.getName(), "The converted DTO's name should equal to expected DTO's name.");
}
What I want to know is if I already assume mockProjectMapper.toDto() will return exactly equaled expected object, why would I need to assert with the actual(returned) object?
I learned that Unit Test should test any codes which can be breakable.
I doubt what's the benefit of this way of the test and if it's inappropriate, what's the correct way to test this method with mocking?
I referenced this test method from this example code.
#refael-sheinker Here's the source code of the ProjectMapper class which is MapStruct's interface class.
#Mapper
public interface ProjectMapper {
ProjectMapper INSTANCE = Mappers.getMapper(ProjectMapper.class);
#Mapping(target = "id", source = "projectId")
#Mapping(target = "name", source = "projectName")
ProjectDto toDto(ModelObject modelObject);
}
You mocked your object under test and stubbed method under test - and you correctly concuded that such a test brings no benefit.
You are checking that you can stub a method, not your production code.
The main problem is your assumption that you need to mock every 3rd party class. This is wrong way of thinking - you should try to mock collaborators of your class (especially ones that cause side-effects unwanted in the test environment), but not the classes which are critical part of implementation of your class. The line might be sometimes blurry, but in this concrete example I strongly recommend NOT mocking the mapper:
your 3rd party class is driven by annotations
the annotations must correspond to your DTOs fields
the field names in annotations are Strings, their presence is DTOs is not enforced by compiler
all mapping is done in the memory
Creating a test that exercises production code gives you confidence that:
field names are correct
corresponding mapped fields have correct types (for example, you are not mapping int to boolean)
The linked test of the service is somewhat different: it tests a Spring service which calls the repository. The repository presumably represents some DB, which interacts with the outside world - it is a good candidate for a mock. To my eyes the service method only forwards the call to the repository - which is a trivial behaviour - so the test is trivial, but this setup will scale for more complicated service methods.
Update
Mocking source class for MapStruct.
If ModelObject is complicated to create, consider using mocks for some of its constructor parameters.
var pN = mock(ParamN.class);
// possibly stub some interactions with pN
var modelObject = new ModelObject(10, 20, pN);
Alternatively, you could even mock entire ModelObect and stub its getters:
var mockModelObject = mock(ModelObject.class);
when(mockModelObject.getId()).thenReturn(10);
I want to pass in suitable object into the verify method, not just any().
Is there a way to do it?
I cannot just take and copy Lambda method and pass the results into the verify. That doesn't work because Lambdas cannot be tested directly.
My Unit test which is obviously not even close to testing anything:
#Test
public void testRunTrigger() {
campaignTrigger.updateCampaignStatus();
verify(jdbcTemplate).update(any(PreparedStatementCreator.class));
assertEquals("UPDATE campaign SET state = 'FINISHED' WHERE state IN ('PAUSED','CREATED','RUNNING') AND campaign_end < ? ", campaignTrigger.UPDATE_CAMPAIGN_SQL);
}
And this is the class I'm testing :
#Component
#Slf4j
public class CampaignTrigger {
final String UPDATE_CAMPAIGN_SQL = String.format("UPDATE campaign SET state = '%s' " +
" WHERE state IN (%s) AND campaign_end < ? ", FINISHED,
Stream.of(PAUSED, CREATED, RUNNING)
.map(CampaignState::name)
.collect(Collectors.joining("','", "'", "'")));
#Autowired
private JdbcTemplate jdbcTemplate;
#Scheduled(cron = "${lotto.triggers.campaign}")
#Timed
void updateCampaignStatus() {
jdbcTemplate.update(con -> {
PreparedStatement callableStatement = con.prepareStatement(UPDATE_CAMPAIGN_SQL);
callableStatement.setTimestamp(1, Timestamp.valueOf(LocalDateTime.now()));
log.debug("Updating campaigns statuses.");
return callableStatement;
});
}
Any advice, or theoretical knowledge that this is not the way to do it I would highly appreciate.
You shouldn't mock the code which you don't control. Mock only the code for which you have your tests, because when mocking you are assuming that you know (i.e. you define) how the mocked class works.
Here, you have no idea how jdbcTemplate works and whether calling it with some lambda actually does what you think it does.
Testing your code with code that you don't control is the point of integration tests. I.e. you should test your CampaignTrigger together with a real database (or in-memory one) and without mocking jdbcTemplate.
You could try your luck with capturing the object that is used for that call, see here. That allows to write code like this:
ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
verify(mock).doSomething(argument.capture());
assertEquals("John", argument.getValue().getName());
Giving you full access to the object that was passed to your method call! And note that mockito recently introduced a #Captor annotation that makes things even easier to use.
Edit; given the comments by #Morfic: what he states is absolutely reasonable.
This answer is giving the "immediate" hint how you could solve that specific problem.
Beyond: the reasonable approach is always always always to slice that "unit under test" ... to be as small as possible!
Your class/method(s) should serve exactly one responsibility; and then you makes sure that the implementation can be tested with most simple means possible.
So: if the question is: "should I use argument captors or should I better rework my production code" - then rework your production code.
I have the following (simplified) function which I wish to check using JUnit:
protected IDfCollection getCollection(IDfSession session) throws DfException{
IDfQuery dfcQuery = new DfQuery();
dfcQuery.setDQL(MY_DQL);
return dfcQuery.execute(session, IDfQuery.READ_QUERY);
}
I have successfully tested it using real IDfSession, but I would like do it without connecting to the repository. So I tried to mock empty IDfSession using:
IDfSession mockedSession = Mockito.mock(IDfSession.class);
But I was given NullPointerException:
Caused by: java.lang.NullPointerException
at java.util.StringTokenizer.<init>(StringTokenizer.java:182)
at java.util.StringTokenizer.<init>(StringTokenizer.java:204)
at com.documentum.fc.internal.util.SoftwareVersion.<init>(SoftwareVersion.java:53)
at com.documentum.fc.client.DfQuery.runQuery(DfQuery.java:136)
at com.documentum.fc.client.DfQuery.execute(DfQuery.java:208)
Not knowing what actually went wrong (which function of the mocked object returned null which was not expected) I created a simple class implementing IDfSession interface and used code coverage tool to check which function was called. I hoped to mock behavior of the function later using mockito. I seemed to be getServerVersion so I changed returned null to real value "6.5.0.355 SP3P0600 Linux.Oracle". Next called function was getBatchManager so I mocked returned object here as well. But now I get:
Caused by: java.lang.ClassCastException: com.example.model.mock.IDfSessionMocked cannot be cast to com.documentum.fc.client.impl.session.ISession
I tried to implement ISession interface in the IDfSessionMocked class, but it does not compile, for instance because one of the used types (namely com.documentum.fc.client.impl.session.ISessionListener) is not visible.
Here: http://www.informedconsulting.nl/blog/?p=187 I found information how to do it using powerMock. Another difference is that object is taken directly from session not using IDfQuery.
What should I do?
Update after comment
getBatchManager function was mocked and now it returns anonymous inner class object, with all the returned values set to false or 0 depending on the expected returned type. Function isFlushBatchOnQuery has been called according to coverage tool.
I am not an expert of Documentum but I think you need a more complex object, you could have a look at this repo https://github.com/ValentinBragaru/dfc-mock
IDfSessionMock is what you need I think.
I hope it helps.
I could not get it to work. It's like the method is not mocked.
Are there alternative groovy testing frameworks that work better to mock static Java methods?
Update 02/Mar/2011: Adding code:
I am actually trying to mock the Scala XML.loadXml (I am trying Groovy for unit testing) class:
This is my test case:
// ContentManagementGatewayTest.groovy
class ContentManagementGatewayTest extends GMockTestCase
{
void testGetFileList()
{
// Preparing mocks code will go here, see below
play {
GetFileGateway gateway = new GetFileGateway();
gateway.getData();
}
}
}
// GetFileGateway.scala
class GetFileGateway {
def getData()
{
// ...
val xmlData = XML.loadData("file1.txt");
}
}
I tried testing using both gmock and metaClass:
// metaClass:
XML.metaClass.'static'.loadFile = {file ->
return "test"
}
// gmock:
def xmlMock = mock(XML)
xmlMock.static.loadFile().returns(stream.getText())
You can do this using Groovy (metaprogramming), you don't need any additional libraries. Here's a (stupid) example, that overrides Collections.max such that it always returns 42. Run this code in the Groovy console to test it.
// Replace the max method with one that always returns 42
Collections.metaClass.static.max = {Collection coll ->
return 42
}
// Test it out, if the replacement has been successful, the assertion will pass
def list = [1, 2, 3]
assert 42 == Collections.max(list)
Update
You mentioned in a comment that my suggestion didn't work. Here's another example that corresponds to the code you've shown in your question. I've tested it in the Groovy console and it works for me. If it doesn't work for you, tell me how your testing differs from mine.
Math.metaClass.static.random = {-> 0.5}
assert 0.5 == Math.random()
Scala doesn't have static methods, so it is no wonder you couldn't mock one -- it doesn't exist.
The method loadXml to which you refer is found on the XML object. You can get that object from Java with scala.XML$.MODULE$, but, since objects are singleton, its class is final.
Alas, loadXML is defined on the class XMLLoader, which the object XML extends, not on the object XML itself. So you can simply do a normal mock of XMLLoader. It will lack a few methods, but perhaps it will do all you need.
The documentation for GMock seems to show that you can just do:
Mocking static method calls and
property call is similar to standard
method calls, just add the static
keyword:
def mockMath = mock(Math)
mockMath.static.random().returns(0.5)
play {
assertEquals 0.5, Math.random()
}
Greetings.
I am mocking a search engine for testing in my web app. This search engine returns xml documents with different schemas. The schema depends on a parameter known as a collection set. Returning different schemas based on collection sets is the part that's difficult to mock, because specifying the collection set is basically a setup method, and a void one at that. This search engine is an external jar file so I can't modify the API. I have to work with what's been provided. Here's an example:
Engine engine = factory.getEngine();
Search search = engine.getSearch();
search.addCollectionSet(someCollectionSet);
SearchResult result = search.getSearchResult();
Document[] documents = result.getAllDocuments();
Then for each document, I can get the xml by calling:
document.getDocumentText();
When I'm using my mock objects, getDocumentText() returns an xml string, created by a generator, that conforms to the schema. What I want to do is use a different type of generator depending on which collection set was provided in step 3 in the first code snippet above. I've been trying to do something like this:
doAnswer(new Answer() {
Object answer(InvocationOnMock invocation) {
if (args == "foo") {
SearchResult result = getMockSearchResult();
when(search.getSearchResult()).thenReturn(result);
}
}
}).when(search.addCollectionSet(anyString()));
But this results in lots of red highlighting :)
Basically, my goal is to key off of addCollectionSet(someCollectionSet) so that when it's called, I can do some kind of switch off of the parameter and ensure that a different generator is used. Does anyone know how I can accomplish something like this? Or is there maybe some form of Dependency Injection that could be used to conditionally wire up my generator?
Thanks!
Update
I've changed my factory object so that it never returns the engine, but rather, the Search and Find objects from that engine, so now I can do something like this:
Search search = factory.getSearch(collectionSet);
So what I'd like to do is something like this:
when(factory.getSearch(anyString()).thenAnswer(new Answer() {
Object answer(InvocationOnMock invocation) {
switch(args[0]) {
case fooSet: return fooSearch; break;
case barSet: return barSearch; break;
In other words, I still want to key off the string that was passed in to getSearch in a switch statement. Admittedly, I could do something more like felix has suggested below, but I'd rather have all my cases wrapped in a switch. Can someone provide an example of how this could be done? Thanks!
Update
I've seen that you can capture the arguments that are passed into mocked calls, but these captured arguments are used for later assertions. I haven't seen a way that I can key off these arguments so that a call to my mock will return different values depending on the arguments. It seems like there has to be a way to do this, I just don't have enough experience with Mockito to figure it out. But surely someone does!
I would recommend wrapping the call to the legacy code into your own object.
So you end up with your own method along these lines:
class SearchEngineWrapper {
public String getSearchResult(String collection){
Engine engine = factory.getEngine();
Search search = engine.getSearch();
search.addCollectionSet(someCollectionSet);
SearchResult result = search.getSearchResult();
...
return document.getDocumentText();
}
}
Now you can mock out this method. The method also nicely documents your intent. Also you could test the actual implementation in an integration test.
when(searchEngineWrapper.getSearchResult("abc").thenReturn("foo");
when(searchEngineWrapper.getSearchResult("xyz").thenReturn("bar");