0

I followed this tutorial (https://spring.io/guides/tutorials/react-and-spring-data-rest/#react-and-spring-data-rest-part-5) to experiment Spring Data REST and I wanted to test the CRUD with TestRestTemplate.

  • Add (postForEntity) is ok.
  • Delete (delete) is ok.
  • Read (getForEntity) is ok.
  • Update (template.exchange(URL, HttpMethod.PUT, entity, String.class, ID)) only works when I don't have any relation with other entities... and I don't understand why.

Here's an example :

@Data
@Entity
public class Dojo {

    private @Id @GeneratedValue Long id;
    private String name;
    private String location;
    private Date created;
    @OneToMany(mappedBy = "dojo")
    @JsonIgnore
    private List<Workshop> workshops;

    private Dojo() {}

    public Dojo(String name, String location) {
        this.name = name;
        this.location = location;
        this.created = new Date();
        this.workshops = new ArrayList<>();
    }
//getters and setters ...
}

@Data
@Entity
public class Workshop {

    private @Id @GeneratedValue Long id;
    private String name;
    @ManyToOne
    private Dojo dojo;

    private Workshop() {}

    public Workshop(String name, Dojo dojo) {
        this.name = name;
        this.dojo = dojo;
    }
}

So, I have a bidirectionnal 1:n relation between Dojo & Workshop. The @JsonIgnore annotation is here to avoid an infinite loop with the JSON Marshaller. The repositories are standard

 public interface WorkshopRepository extends CrudRepository<Workshop, Long> {}

Now my test : I want to update a workshop. Sounds good, doesn't work.

@Test
public void testUpdateWorkshop() throws Exception {

    final String DOJO_NAME="My Dojo";
    final String DOJO_LOCATION="Liege";
    final String WORKSHOP_NAME="Stuff";
    final String HOST_PORT="http://localhost:8080";

     //creation of a dojo
    final Dojo DOJO = dojoRep.save(new Dojo(DOJO_NAME,DOJO_LOCATION));
     //creation of a workshop
    Workshop workshop = workshopRep.save(new Workshop(WORKSHOP_NAME,DOJO));

    String newValue = "After Test";

    System.out.println("before update");
    System.out.println(workshop.getName()+" == "+WORKSHOP_NAME);

    Long oldID = workshop.getId();

    //As you can see I didn't modify the workshop object
    HttpEntity<Workshop> entity = new HttpEntity<Workshop>(workshop);
    ResponseEntity<String> response = template.exchange(HOST_PORT+"/api/workshops/"+oldID, HttpMethod.PUT, entity, String.class, oldID);

    assert response.getStatusCodeValue() == 200;

    //re-Get the updated workshop
    workshop = workshopRep.findOne(oldID);

    System.out.println("after update");
    System.out.println(workshop.getName()+" == "+WORKSHOP_NAME);

    // as I didn't set the newValue, it must fail and workshop.getName() must stay equal to "Stuff".
    Assert.assertEquals("Update does not work",newValue,workshop.getName());
}

I run mvn clean test and

before update
Stuff == Stuff

after update
My Dojo == Stuff

Failed tests: 
  WorkshopTests.testUpdateWorkshop:218 Update not work expected:<[After Test]> but was:<[My Dojo]>

So basically, I didn't change anything into my object but

  1. Result code is 200.
  2. It changed a property of my object.
  3. The name was modified to take the dojo.name value !

Just ... Why ?

More information :

  • When I create a new workshop object with a new name (using the newValue ;-) ) and a new Dojo and try to update the existing workshop, the result is still the same. workshop.dojo unchanged and name copied from dojo.name. So basically, my update doesn't work.
  • I also try with mockMvc instead of TestRestTemplate like this.

    mockMvc.perform(put(HOST_PORT+"/api/workshops/"+oldID)
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .content(convertObjectToJsonBytes(workshop))
    );
    

    with the function

    private byte[] convertObjectToJsonBytes(Object object) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        System.out.println("log my face ");
        System.out.println(mapper.writeValueAsString(object));
        return mapper.writeValueAsBytes(object);
    }
    

    And the log seems to rightly parse my object before update...

    {"id":1,"name":"Stuff","dojo":{"id":1,"name":"My Dojo","location":"Liege","created":1500799092330}}
    

    but still doesn't work :(

  • When I run the app (mvn spring-boot:run), a GET on localhost:8080/api/workshops/1 returns

    {
      "name" : "Stuff",
      "_links" : {
        "self" : {
          "href" : "http://localhost-core:8080/api/workshops/1"
        },
        "workshop" : {
          "href" : "http://localhost-core:8080/api/workshops/1"
        },
        "dojo" : {
          "href" : "http://localhost-core:8080/api/workshops/1/dojo"
        }
      }
    }
    
  • If I change the property name of my Dojo class by nameD and I update with a new name and a new Dojo (previously saved into DB), the name is updated but not the dojo.

To summarize my questions are :

  • Just ... why ?
  • What is the correct way to update an object like Workshop with a HTTP request ?
  • What is the correct way to test this update ?

Thanks to all and have a nice day ! :-)

1 Answers1

0

I think it's because you are using bidirectional one-to-many association. In this case you have to provide linking/unlinking of entities by yourself. For example in the collection setter, like this:

@Data
@ToString(exclude = "slaves")
@Entity
public class Master {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "master", cascade = {PERSIST, MERGE})
    private List<Slave> slaves;

    public void setSlaves(List<Slave> slaves) {

        // link new slaves to this master
        slaves.forEach(slave -> slave.setMaster(this)); 

        // unlink prev slaves
        if (this.slaves != null) this.slaves.forEach(slave -> slave.setMaster(null)); 

        this.slaves = slaves;
    }
}

@Data
@Entity
public class Slave {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    private Master master;
}

Then you can store Slave:

POST http://localhost:8080/api/slaves
{
    "name": "slave1"
}

// the same for salve2, slave3, slave4

Store Master:

POST http://localhost:8080/api/masters
{
    "name": "master1", 
    "slaves": [
        "http://localhost:8080/api/slaves/1",
        "http://localhost:8080/api/slaves/2"
        ]
}

Update Master:

PUT http://localhost:8080/api/masters/1
{
    "name": "master1u", 
    "slaves": [
        "http://localhost:8080/api/slaves/3",
        "http://localhost:8080/api/slaves/4"
        ]
}

PUT http://localhost:8080/api/masters/2
{
    "name": "master2"
}

Or update Slave:

PUT http://localhost:8080/api/slaves/1
{
    "name": "slave1u", 
    "master": "http://localhost:8080/api/masters/2"
}

PUT http://localhost:8080/api/slaves/2
{
    "name": "slave2u", 
    "master": "http://localhost:8080/api/masters/2"
}

See working example.

Additional info

Cepr0
  • 28,144
  • 8
  • 75
  • 101
  • Thanks. I have the correct result with the curl commands. But my try to do tests with the TestRestTemplate are still incorrect. :/ – GeekTortoise Jul 24 '17 at 18:16