3

I am testing a controller using MockMvc. This is what the response looks like:

MockHttpServletResponse:
              Status = 200
       Error message = null
             Headers = {Content-Type=[text/xml]}
        Content type = text/xml
                Body = <?xml version="1.0" encoding="UTF-8" standalone="yes"?><ns2:diagnosisCode xmlns:ns2="http://schemas.mycompany.co.za/health" effectiveStartDate="2014-03-05T00:00:00+02:00" effectiveEndDate="2014-03-05T23:59:59.999+02:00" diagnosisId="1"><diagnosisCodeId><codingSchemaCode>irrelevant schema</codingSchemaCode><diagnosisCode>irrelevant code</diagnosisCode></diagnosisCodeId></ns2:diagnosisCode>
       Forwarded URL = null
      Redirected URL = null
             Cookies = []

Pretty-printed version of the Body line:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:diagnosisCode xmlns:ns2="http://schemas.mycompany.co.za/health" effectiveStartDate="2014-03-05T00:00:00+02:00" effectiveEndDate="2014-03-05T23:59:59.999+02:00" diagnosisId="1">
    <diagnosisCodeId>
        <codingSchemaCode>irrelevant schema</codingSchemaCode>
        <diagnosisCode>irrelevant code</diagnosisCode>
    </diagnosisCodeId>
</ns2:diagnosisCode>

The call on MockMvc looks like

mockMvc.perform(
        get("/diagnostic/diagnosisCodes/{schema}/{code}", IRRELEVANT_SCHEMA, IRRELEVANT_CODE).accept(MediaType.TEXT_XML))
        .andDo(print())
        .andExpect(content().contentType(MediaType.TEXT_XML))
        .andExpect(status().isOk())
        .andExpect(xpath("diagnosisCodeId/diagnosisCode").string(IRRELEVANT_CODE))
        .andExpect(xpath("diagnosisCodeId/codingSchemaCode").string(IRRELEVANT_SCHEMA));

I am pretty sure I am misunderstanding how I'm supposed to use XPath here, but why is this assertion failing? What should my expectation look like?

java.lang.AssertionError: XPath diagnosisCode expected:<irrelevant code> but was:<>
pnuts
  • 58,317
  • 11
  • 87
  • 139
Niel de Wet
  • 7,806
  • 9
  • 63
  • 100

3 Answers3

2

I'm not totally sure what the XPath context is (or whether it is the document node), but I see two possible problems and guess both apply:

  • You try to match < diagnosisCodeId/> elements that are the root element. There are none, but they're children of <diagnosisCode>. Either include an axis step for the root node (probably better way) or use the descendant-or-self axis step // in front of the query.

    /diagnosisCode/diagnosisCodeId/diagnosisCode
    //diagnosisCodeId/diagnosisCode
    
  • The document uses namespaces (for the root element). In addition to the root element problem described above, either register that namespace (better solution, but I don't know how to do this in spring MVC) or ignore it using following workaround:

    /*[local-name() = 'diagnosisCode']/diagnosisCodeId/diagnosisCode
    

    Which first matches all child nodes, but then limits to the ones having the apropriate element name (ignoring namespaces).

    By adding XPath 2.0 support (for example by including Saxon as library), you can also use the wildcard namespace matcher:

    /*:diagnosisCode/diagnosisCodeId/diagnosisCode
    

    If you register the namespace URI http://schemas.mycompany.co.za/health as ns2, the query would look like

    /ns2:diagnosisCode/diagnosisCodeId/diagnosisCode
    
Community
  • 1
  • 1
Jens Erat
  • 37,523
  • 16
  • 80
  • 96
  • 2
    There is also a simpler formulation of `/*[local-name() = 'diagnosisCode']/*[local-name() = 'diagnosisCodeId']/*[local-name() = 'diagnosisCode']` which is `/*:diagnosisCode/*:diagnosisCodeId/*:diagnosisCode` – adamretter Mar 05 '14 at 23:42
  • Does spring mvc support XPath 2.0? [Following to this answer](http://stackoverflow.com/questions/16242708/how-do-i-change-the-xslt-provider-for-spring-mvc-i-e-plug-in-saxon-9-x) it seems so, I'll add that to the answer. – Jens Erat Mar 06 '14 at 09:23
  • @adamretter Unfortunately that isn't working for me. – Niel de Wet Mar 06 '14 at 09:27
  • 1
    @JensErat I've got it all working now. The only thing is that the namespace prefix should only be on the root element. Please edit your answer. – Niel de Wet Mar 06 '14 at 09:40
  • You're right, I overlooked that the namespaces was only used in the element and no default namespaces applied. Next time, better also add some pretty-printed version of the XML, makes things easier. – Jens Erat Mar 06 '14 at 10:13
1

There is an overload for xpath that takes a Map<String, String> of namespaces:

Map<String, String> ns = Map.of("ns2", "http://schemas.mycompany.co.za/health");
mockMvc.perform(get("/diagnostic/diagnosisCodes/{schema}/{code}", IRRELEVANT_SCHEMA, IRRELEVANT_CODE)
        .accept(MediaType.TEXT_XML))
        .andExpect(xpath("ns2:diagnosisCodeId/diagnosisCode", ns).string(IRRELEVANT_CODE))
        .andExpect(xpath("ns2:diagnosisCodeId/codingSchemaCode", ns).string(IRRELEVANT_SCHEMA));
OrangeDog
  • 36,653
  • 12
  • 122
  • 207
0

You have to include this in your pom.xml:

    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>

I have a rest controller:

    @RestController
    public class AccountController {

        @GetMapping(value = "/accounts/{acctId}" , produces = {MediaType.APPLICATION_JSON_VALUE,  MediaType.APPLICATION_XML_VALUE})
        public Account accountDetails(@PathVariable int acctId) {
            return retrieveAccount(acctId);
        }
    }

and in my JUnit test class:

    @Autowired
    MockMvc mockMvc;

    String accountNumber = "1234567890";
    String customerName = "John Doe";

    @Test
    public void accountDetails_XML () throws Exception
    {
        when ( accountManager.getAccount ( 0L ) ).thenReturn ( new Account ( accountNumber, customerName ) );

        mockMvc.perform ( get ( "/accounts/0" ).accept ( MediaType.APPLICATION_XML_VALUE ) ).andExpect (
                                                content ().contentType ( MediaType.APPLICATION_XML_VALUE ) )
                                                .andExpect ( xpath ( "Account/name" ).string ( customerName ) )
                                                .andExpect ( xpath ( "Account/number" ).string ( accountNumber ) )
                                                .andExpect ( status ().isOk () );

        verify ( accountManager ).getAccount ( 0L );

    }

Note: This is what the returned XML looks like:

    <Account>
        <entityId/>
        <number>1234567890</number>
        <name>John Doe</name>
        <beneficiaries/>
        <valid>false</valid>
    </Account>

This is the equivalent test in JSON just for reference.

    @Test
    public void accountDetails_JSON () throws Exception
    {
        when ( accountManager.getAccount ( 0L ) ).thenReturn ( new Account ( accountNumber, customerName ) );

        mockMvc.perform ( get ( "/accounts/0" ) ).andExpect ( status ().isOk () )
                                                .andExpect ( content ().contentType ( MediaType.APPLICATION_JSON ) )
                                                .andExpect ( jsonPath ( "name" ).value ( customerName ) )
                                                .andExpect ( jsonPath ( "number" ).value ( accountNumber ) );

        verify ( accountManager ).getAccount ( 0L );
    }

These sites were helpful:

Lyle Z
  • 1,269
  • 10
  • 7