1
0
-1

I'm writing unit tests on Transact SDK 17.10 for a service that calls another external service more than once.

MockRegister appears to not create a unique entry for a combination of service name, client code and params.

Is there a suggested way to mock the service invoke so I can test this service works from end to end?

    CommentAdd your comment...

    3 answers

    1.  
      4
      3
      2

      Hi Sean


      With the current implementation there is a compare of only those fields:

      new EqualsBuilder()
              .append(serviceName, svcDefKey.serviceName)
              .append(version, svcDefKey.version)
              .append(isCurrentVersion, svcDefKey.isCurrentVersion)
              .append(clientCode, svcDefKey.clientCode)
              .isEquals();

      We are going to evaluate impact and implement a fix with the params in next maintenance release.


      Regards
      Rado

      1. Sean Colyer

        Rado, we are using a fairly complex params object in our calls - it's a map but has child maps. Just FYI as you consider the approach.

      2. Radoslav Ivanov

        Sean Colyer, thanks for the note.

        If it is passed the same paramsMap object (when constructing the SvcDef and when calling invoke method), it should not be a problem, because it is the same object reference.

        There might be a problem if we pass a new map and have to compare the keys/values, but they are String-s, so we will consider that..

        BTW you have to change the key from params to paramsMap:

        new SvcDef([
                "name": "My Great Service",
                "clientCode": svcDef.clientCode,
                "paramsMap": firstParams
        ])



      3. Sean Colyer

        Radoslav - it's unlikely the unit test and service will have the same object reference as they operate within entirely different contexts, so some kind of deep equality comparison will be required.

      4. Sean Colyer

        Here's an example of the type of map I am passing in:

        [
        	"DateOfBirth": "1996-03-05",
        	"FirstName": "John",
        	"LastName": "Robinson",
        	"MiddleName": "Keith",
        	"OtherName": "Boris",
        	"TitleID": 3,
        	"Gender": "Male",
        	"ResidentialAddress": [
        		"UnitNumber": "1",
        		"UnitType": "Box",
        		"PostCode":"4454",
        		"PropertyName": null,
        		"State": "SA",
        		"StreetName": "Lamington",
        		"StreetNumber": "44",
        		"StreetType": "Lane",
        		"Suburb": "Sweetshire",
        	],
        	"PostalAddressIsResidentialAddress": false,
        	"PostalAddress": [
        		"UnitNumber": "1",
        		"UnitType": "Box",
        		"PostCode":"4454",
        		"PropertyName": null,
        		"State": "SA",
        		"StreetName": "Lamington",
        		"StreetNumber": "44",
        		"StreetType": "Lane",
        		"Suburb": "Sweetshire",
        	],
        	"Phone": [
        		"Mobile": "0404840677",
        		"Home" : "0883543137",
        		"Business" : "45646464"
        	],
        	"Email": "example@example.com",
        	"TFN": "123456789"
        ]
      5. Sean Colyer

        I reckon you could do a recursive check using instanceof against the map. I.e. if it's a String, do a shallow comparison, if it's a map, iterate through and check for string or map etc.

      6. Radoslav Ivanov

        This should do the job, right?

        private static boolean equalMaps(Map map1, Map map2) {
                if (map1 == null && map2 == null) {
                    return true;
                }
                if ((map1 != null && map2 == null) || (map1 == null && map2 != null)) {
                    return false;
                }
                if (map1.size() != map2.size()) {
                    return false;
                }
                return map1.entrySet().containsAll(map2.entrySet());
            }
      7. Sean Colyer

        Yes, that looks good Rado.

        For reference, I wrote the following unit test to confirm it will work as expected:

        Map one = [:]
        one.First = '1'
        one.Second = [:]
        one.Second.Third = '3'
        
        Map two = [:]
        two.First = '1'
        two.Second = [:]
        two.Second.Third = '3'
        
        Map three = [:]
        three.First = '1'
        three.Second = [:]
        three.Second.Third = '5'
        
        assert one.entrySet().containsAll(two.entrySet())
        assert !one.entrySet().containsAll(three.entrySet())
      8. Sean Colyer

        When can we expect the next maintenance release?

      9. Radoslav Ivanov

        May be with TM 17.10.6 in early June.

      CommentAdd your comment...
    2.  
      1
      0
      -1

      You might be able to mock your registry, make a service call, and then clear your registry by calling mockRegister.clear() between calls, and repeat the process.

      I haven't tried this, but it seems like it might work.

      1. Sean Colyer

        Thanks Donald. I don't think this will work. I have tried to mock the registry already but it turns out that ServiceInvoker checks the MockRegistry using a four part key that does not include the params.

        I can't inject my mock into the ServiceInvoker so I can't override this.

        Also, in our implementation, one of our services is called in a loop, so I can't call mockRegister.clear() after every service call as my unit test can't see the loop.

      CommentAdd your comment...
    3.  
      1
      0
      -1

      Hi Sean, 

      Does this answer your query?

      Transact Manager 5.1.7 Release Notes

      1. Sean Colyer

        Hi Christopher,

        No it doesn't. I'm also using 17.10 and have already been using MockRegister effectively.

        Consider the following code:

        String responseOne = 'ONE'
        String responseTwo = 'TWO'
        MockRegister mockRegister = new MockRegister()
        Map firstParams = [:]
        firstParams.Foo = 'Bar'
        SvcDef firstCallDefinition = new SvcDef([
                "name": "My Great Service",
                "clientCode": svcDef.clientCode,
                "params": firstParams
        ])
        mockRegister.when(firstCallDefinition).thenReturn(responseOne)
        
        Map secondParams = [:]
        secondParams.Fizz = 'Buzz'
        SvcDef secondCallDefinition = new SvcDef([
                "name": "My Great Service",
                "clientCode": svcDef.clientCode,
                "params": secondParams
        ])
        mockRegister.when(secondCallDefinition).thenReturn(responseTwo)
        
        
        // Implementation from invoked service
        String firstResponse = new ServiceInvoker()
                .setServiceName("My Great Service")
                .invoke(firstParams)
        
        
        // Expected: Return 'ONE' Actual: Return 'TWO'
        
        
        String secondResponse = new ServiceInvoker()
                .setServiceName("My Great Service")
                .invoke(secondParams)
        
        
        // Expected: Return 'TWO' Actual: Return 'TWO'

        I want to know how to make My Great Service return 'ONE' when called with firstParams and 'TWO' when called with secondParams.

      2. Radoslav Ivanov

        Hi Sean

        I think there is some misusage in your example. Please note you have two types of parameters - service definition and service invocation ones. In your example, you pass 'firstParams' to construct the service definition and then wrongly pass the same map as invocation arguments map (which would not map to the service one)

        We implemented mapping a service def map and evaluating also mapping on invocation parameters in the next releases.

        Therefore, your example should be:

        // Expected: Return 'ONE'
        String firstResponse = new ServiceInvoker()
                .setSvcDef(firstCallDefinition)
                .invoke()
        
        
        // Expected: Return 'TWO'
        String secondResponse = new ServiceInvoker()
                .setSvcDef(secondCallDefinition)
                .invoke()


        Regards
        Rado

      3. Sean Colyer

        Thanks Rado. I won't have access to the exact svcDef object I use in my test in my class under test though - but you are correct that the fragment above should also include the client code so that the svcDef params match up when the MockRegister is called.

        Is that what you were getting at?

      CommentAdd your comment...