1

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

GodLike
  • 19
  • 5

3 Answers3

0

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 :-)

Refael Sheinker
  • 713
  • 7
  • 20
  • 1
    The validation is working fine when I try adding a note with invalid input using an http client but it isn't working only when testing the method with Mockito and JUnit – GodLike Feb 24 '22 at 15:54
0

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")));
    }
    }
  }
pringi
  • 3,987
  • 5
  • 35
  • 45
  • The validation is working fine when I try adding a note with invalid input using an http client but it isn't working only when testing the method with Mockito and JUnit – GodLike Feb 24 '22 at 15:52
0

So your noteRepository is Mocked, so you it's not actually calling save on your repository.

Mockito.verify(noteRepositoryMock).save(note);

All you are verifying here is that a call to save is made, not that it was successful.

Justin
  • 1,356
  • 2
  • 9
  • 16