Home > Mobile >  repoistory.save() getting invoked with invalid entry when unit testing
repoistory.save() getting invoked with invalid entry when unit testing

Time:02-24

I'm using java validation API to validate fields in my Note class:

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "note")
public class Note {

    @Id
    @Column(name = "id", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "date", columnDefinition = "DATE")
    private LocalDate date;

    @NotBlank(message = "Enter a topic")
    @Column(name = "topic")
    private String topic;

    @NotBlank(message = "Content can't be empty")
    @Column(name = "content")
    private String content;

    @Column(name = "type")
    private NoteType noteType;

    @NotNull
    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinColumn(name = "user_id")
    @JsonIgnore
    private User user;
}

NoteService:

@Service
@AllArgsConstructor
public class NoteService {

    @Autowired
    private NoteRepository noteRepository;

    @Autowired
    private UserRepository userRepository;


    public void addNote(@Valid Note note) {
        note.setUser(getLoggedInUser());
        if (validateNote(note)) {
            noteRepository.save(note);
        }
    }

    public List<Note> getNotes() {
        return getLoggedInUser().getNotes();
    }

    public Note editNote(Note newNote, Long id) {
        noteRepository.editNoteById(newNote, id);
        return newNote;
    }

    public List<Note> getNotesByTopic(String topic) {
        List<Note> notes = noteRepository.getNotesByTopicAndUser(topic, getLoggedInUser());
        return notes;
    }

    public boolean validateNote(Note note) {
        return  validateNoteType(note.getNoteType())
                && note.getDate() != null;
    }

    public boolean validateNoteType(NoteType type) {
        return type.equals(NoteType.NOTE)
                || type.equals(NoteType.SKILL);
    }

    public User getLoggedInUser() {
        return userRepository.findByEmail(SecurityContextHolder.getContext().getAuthentication().getName());
    }
}

Test:

@ExtendWith(MockitoExtension.class)
@ExtendWith(SpringExtension.class)
class NoteServiceTest {

    @Mock
    private NoteRepository noteRepositoryMock;
    @Mock
    private UserRepository userRepositoryMock;
    @Mock
    SecurityContext mockSecurityContext;
    @Mock
    Authentication authentication;
    private NoteService noteService;

    @BeforeEach
    void setUp() {
        noteService = new NoteService(noteRepositoryMock, userRepositoryMock);
        Mockito.when(mockSecurityContext.getAuthentication()).thenReturn(authentication);
        SecurityContextHolder.setContext(mockSecurityContext);
    }

    @Test
    void shouldAddNote() {
        LocalDate date = LocalDate.now();
        Note note = new Note(0L, date, "test", "", NoteType.NOTE, null);
        noteService.addNote(note);
        Mockito.verify(noteRepositoryMock).save(note);
    }
}

The field user in the Note class is annotated with @NotNull and I'm passing a null user to this note but the note is still getting saved. Same thing when I pass an empty string. Any idea why that is happening? I'm new to unit testing

CodePudding user response:

I'm new to unit testing - your perfectly valid question has nothing to do with unit testing. @NotNull does nothing on it own. Its actually a contract stating the following:

A data member (or anything else annotated with @NotNull like local variables, and parameters) can't be should not be null.

For example, instead of this:

/**
 * @param obj should not be null
 */
public void MyShinyMethod(Object obj)
{
    // Some code goes here.
}

You can write this:

public void MyShinyMethod(@NotNull Object obj)
{
    // Some code goes here.
}

P.S.
It is usually appropriate to use some kind of annotation processor at compile time, or something that processes it at runtime. But I don't really know much about annotation processing. But I am sure Google knows :-)

CodePudding user response:

You need to activate validation on you service class with the @Validated annotation so the validation of parameters kicks in.

@Service
@AllArgsConstructor
@Validated
public class NoteService {
...

See Spring @Validated in service layer and Spring Boot: How to test a service in JUnit with @Validated annotation? for more details.

If for some reason you need to manually perform the validation you can always do something like this:

@Component
public class MyValidationImpl {

  private final LocalValidatorFactoryBean validator;

  public MyValidationImpl (LocalValidatorFactoryBean validator) {

    this.validator = validator;
  }

  public void validate(Object o) {
    Set<ConstraintViolation<Object>> set = validator.validate(o);

    if (!set.isEmpty()) {
      throw new IllegalArgumentException(
          set.stream().map(x -> String.join(" ", x.getPropertyPath().toString(), x.getMessage())).collect(
              Collectors.joining("\n\t")));
    }
    }
  }
  • Related