I am attempting to use EasyMock alongside JUnit and have run into difficulties while scheduling method calls on a mocked dependency in a JUnit 4 #Before method.
In the below example the test class MockWithBeforeTest is testing the class ClassUnderTest. Dependency is passed to ClassUnderTest's constructor, in which one of Dependency's methods is called, returning a value needed to initialise ClassUnderTest. This process of initialising ClassUnderTest will be the same for all tests, so I decorate the ClassUnderTest#setUp method with a JUnit 4 #Before annotation.
When testing the method ClassUnderTest#getDerived we expect a call to the mocked Dependency instance to return a value, which we schedule in the method MockWithBeforeTest#testGetDerived. However, this test unexpectedly fails with the error Unexpected method call Dependency.getB() despite the fact that this call is scheduled in MockWithBeforeTest#testGetDerived.
How should I modify the example code such that MockWithBeforeTest#testGetDerived passes?
Example code
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import org.easymock.EasyMockRule;
import org.easymock.Mock;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class MockWithBeforeTest {
#Rule
public EasyMockRule rule = new EasyMockRule(this);
#Mock
private Dependency dependency;
private ClassUnderTest classUnderTest;
#Before
public void setUp() {
expect(this.dependency.getA()).andReturn(2);
replay(this.dependency);
this.classUnderTest = new ClassUnderTest(this.dependency);
verify(this.dependency);
}
#Test
public void testGetDerived() {
expect(this.dependency.getB()).andReturn(3);
replay(this.dependency);
assertEquals(6, this.classUnderTest.getDerived(1));
verify(this.dependency);
}
}
class ClassUnderTest {
private int a;
private Dependency dependency;
ClassUnderTest(Dependency dependency) {
this.a = dependency.getA();
this.dependency = dependency;
}
void setA(int val) {
this.a = val;
}
int getDerived(int val) {
return val * this.a * this.dependency.getB();
}
}
class Dependency {
private int a;
private int b;
Dependency(int a, int b) {
this.a = a;
this.b = b;
}
int getA() {
return this.a;
}
int getB() {
return this.b;
}
}
Stack Trace
java.lang.AssertionError:
Unexpected method call Dependency.getB():
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:44)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:101)
at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:97)
at Dependency$$EnhancerByCGLIB$$6d3a4341.getB(<generated>)
at MockWithBeforeTest.testGetDerived(MockWithBeforeTest.java:33)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.easymock.internal.EasyMockStatement.evaluate(EasyMockStatement.java:43)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
Why not create an instance of Dependency to pass to the constructor?
The example given above is representative of a general problem in which the dependency passed to the class under test is much more complex than Dependency.
Speculation about design problem
I am mindful of the fact that details of the class under test's implementation are leaking into the test class via the scheduled methods on the mocked dependency. However I am not experienced enough with mocking frameworks to tell if this is an unavoidable side effect of mocking, or a symptom of a flaw in my design. Any guidance on this would be appreciated.
Software version information
Java: 1.8.0_201
JUnit: 4.12
EasyMock: 4.2
More research and discussion with colleagues produced a solution. The step I missed is to reset the mocked Dependency object using EasyMock.reset(this.dependency) to allow additional expected calls to be added in the test methods. The fixed MockWithBeforeTest is
public class MockWithBeforeTest {
#Rule
public EasyMockRule rule = new EasyMockRule(this);
#Mock
private Dependency dependency;
private ClassUnderTest classUnderTest;
#Before
public void setUp() {
expect(this.dependency.getA()).andReturn(2);
replay(this.dependency);
this.classUnderTest = new ClassUnderTest(this.dependency);
verify(this.dependency);
reset(this.dependency); // Allow additional expected method calls to be specified
// in the test methods
}
#Test
public void testGetDerived() {
expect(this.dependency.getB()).andReturn(3);
replay(this.dependency);
assertEquals(6, this.classUnderTest.getDerived(1));
verify(this.dependency);
}
}
The replay() must be called only once, after everything is recorded. That's why it doesn't work here.
Since, in your case you are using the mock in the constructor, you need to instantiate the tested class after the replay.
public class MockWithBeforeTest {
#Rule
public EasyMockRule rule = new EasyMockRule(this);
#Mock
private Dependency dependency;
private ClassUnderTest classUnderTest;
#Before
public void setUp() {
expect(this.dependency.getA()).andReturn(2);
}
#Test
public void testGetDerived() {
expect(this.dependency.getB()).andReturn(3);
replay(this.dependency);
this.classUnderTest = new ClassUnderTest(this.dependency);
assertEquals(6, this.classUnderTest.getDerived(1));
verify(this.dependency);
}
}
You could also reset the mock in between but I'm not sure I like that. It will let you pass the constructor and then reuse the mock for the actual test with a new recording.
public class MockWithBeforeTest {
#Rule
public EasyMockRule rule = new EasyMockRule(this);
#Mock
private Dependency dependency;
private ClassUnderTest classUnderTest;
#Before
public void setUp() {
expect(this.dependency.getA()).andReturn(2);
replay(this.dependency);
this.classUnderTest = new ClassUnderTest(this.dependency);
reset(this.dependency);
}
#Test
public void testGetDerived() {
expect(this.dependency.getB()).andReturn(3);
replay(this.dependency);
assertEquals(6, this.classUnderTest.getDerived(1));
verify(this.dependency);
}
}
I am building webapplication in MVCS structure.
I got all methods with logic inside Service class.
How i can test it using junit? Can some one explain to me how should it work? Or give a course / article? I tried alot of resources, but non of them cover my case.
I am also open for sugestion, maybe my design is bad here...
When I am makeing methods public static it works, however I am not sure if that should be the case.
When I am trying to #Autowire service field I am getting NullPointerException :(
#Service
public class MapValueServiceImpl implements MapValueService {
methodThatDoesSomething() {
}
}
class MapValueServiceImplTest {
#Test
public void TestingMethodThatDoesSomething() {
assertEquals(Something, methodThatDoesSomething())
}
}
Thanks!
For test MVC I can recommend you this tutorial from Spring
https://spring.io/guides/gs/testing-web/
you can check other tutorials in this site. I hope it's help to you.
Consider this example service:
#Service
public class MapValueServiceImpl implements MapValueService {
#Autowired private GreetingService greetingService;
String getGreeting(String name) {
return greetingService.buildGreeting(name);
}
}
If you bring in mockito (assuming you're using Maven):
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
Then you can do something like this...
#RunWith(MockitoJUnitRunner.class)
public class MapValueServiceImplTest {
#Mock private GreetingService greetingService; // <-- define your mocks
#InjectMocks private MapValueServiceImpl toTest; // <-- your mocks will be injected
// Now you can test dependency interaction...
#Test
public void whenNamePassedThenCorrectGreetingReturned() {
// Arrange
final String name = "George";
final String greeting = "Hello " + name;
doReturn(greeting).when(greetingService).buildGreeting(name);
// Act
final String result = toTest.getGreeting(name);
// Assert
assertThat(result, is(greeting));
}
}
This is a pure unit test (and does not require a Spring context). You can find more information on Mockito here: https://site.mockito.org/
I'm currently writing JUnit unit-tests for a class in an Application that uses Spring and AspectJ. The class under test has a couple public methods that are advised by an around-advice method in an aspect class. The Aspect has a couple of injected fields that turn up null when the advised method is executed, even though I've successfully instantiated those beans in the test application context, and when their methods are called, they throw nullpointerexceptions. Here's a simplified version of the code:
The class to be tested:
public class ClassUnderTest {
#Inject
private Foo foo;
#Audit(StringValue="arg", booleanValue=true)
public Object advisedMethod() {
Object ret = new Object();
//things happen
return ret;
}
The Aspect:
#Aspect
#Configurable
public class AuditAspect implements Versionable {
#Inject
Foo foo;
#Inject
Bar bar;
#Around("#annotation(Audit)")
public Object aroundAdvice(ProceedingJoinPoint pjp, Audit audit) {
// Things happen
privMethod(arg);
// Yet other things happen
Object ret = pjp.proceed();
// Still more things happen
return ret;
}
private Object privMethod(Object arg) {
// Things continue to happen.
// Then this throws a NullPointerException because bar is null.
bar.publicBarMethod(arg2);
// Method continues ...
}
}
The Audit interface:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Audit {
String value() default "";
boolean bool() default false;
}
The Config file that provides the application context:
import static org.mockito.Mockito.*;
#Configuration
public class ClassUnderTestTestConfig {
#Bean
Foo foo() {
return mock(Foo.class);
}
#Bean
Bar bar() {
return mock(Bar.class);
}
#Bean
ClassUnderTest classUnderTest() {
return new ClassUnderTest();
}
#Bean
#DependsOn({"foo", "bar"})
Aspect aspect() {
return new Aspect();
}
}
The Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextHierarchy({
#ContextConfiguration(classes = ClassUnderTestTestConfiguration.class),
#ContextConfiguration(classes = ClassUnderTest.class)
})
public class ClassUnderTestTest {
private static final String sessionNumber = "123456";
#Inject
Foo foo;
#Inject
Bar bar;
#Inject
ClassUnderTest classUnderTest;
#Inject
Aspect aspect;
#Test
public void test() {
// This call triggers the advice which ends up throwning
// a NullPointerException.
classUnderTest.advised();
}
}
I've also tried making my own Spring proxy and then adding the aspect to it manually as advised in this stack overflow post, by adding this code to the test class:
#Before
public void setUp() {
DataPointRestWebService target = new DataPointRestWebService();
AspectJProxyFactory proxyMaker = new AspectJProxyFactory(target);
proxyMaker.addAspect(auditAspect);
dataPointRestWebService = proxyMaker.getProxy();
}
However that ends up throwing:
AopConfigException: Advice must be declared inside an aspect type. Offending method 'public java.lang.Object Aspect.aroundAdvice' in class [Aspect]
I find this cryptic because I the Aspect class does have the #Aspect annotation before it, and the class works outside of a test environment.
I'm very new to Spring and AspectJ, so I'm completely open to the notion that I'm going about this all wrong. I've provided dummy code here in hopes of leaving out unhelpful specifics, but also because the working code is proprietary and not mine. If you think I've left out an important detail, let me know and I'll try to add it.
Thanks in advance for any help, and please let me know if I'm leaving out any crucial information.
EDIT:
By request, I've added the full NullPointerException stack trace:
java.lang.NullPointerException
at com.unifiedcontrol.aspect.AuditAspect.getCaller(AuditAspect.java:265)
at com.unifiedcontrol.aspect.AuditAspect.ajc$inlineAccessMethod$com_unifiedcontrol_aspect_AuditAspect$com_unifiedcontrol_aspect_AuditAspect$getCaller(AuditAspect.java:1)
at com.unifiedcontrol.aspect.AuditAspect.aroundAuditAdvice(AuditAspect.java:79)
at com.unifiedcontrol.server.rest.DataPointRestWebServiceTest.dummyTest(DataPointRestWebServiceTest.java:109)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:233)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:87)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:176)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Where AuditAspect == Aspect and DataPointRestWebService == ClassUnderTest.
Here's what ended up solving our problem:
We changed the following method in the ClassUnderTestTestConfig class:
#Bean
#DependsOn({"foo", "bar"})
Aspect aspect() {
return new Aspect();
}
to:
#Bean
#DependsOn({"foo", "bar"})
Aspect aspect() {
return Aspects.aspectOf(Aspect.class);
}
for which we added the following import statement:
import org.aspectj.lang.Aspects;
The original code successfully returned a new Aspect object, however when ClassUnderTest.advisedMethod() was called, the jointpoint was delegated to a different Aspect object that hadn't been injected with non-null foo and bar member. Something about how the Aspects.aspectOf() method works ensures that the Aspect object created by TestConfig is the one that provides advice to the call to advisedMethod().
At the moment I have no idea why this solved the problem. Someone else at work found the solution. I plan to look into it and edit this post with more information, but in the mean time all contributions are welcome.
I need to test the validation annotations but it looks like they do not work. I am not sure if the JUnit is also correct. Currently, the test will be passed but as you can see the specified email address is wrong.
JUnit
public static void testContactSuccess() {
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
System.err.println(contact);
}
Class to be tested
public class Contact {
#NotNull
#Size(min = 1, max = 10)
String name;
#NotNull
#Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\."
+"[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#"
+"(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?",
message="{invalid.email}")
String email;
#Digits(fraction = 0, integer = 10)
#Size(min = 10, max = 10)
String phone;
getters and setters
}
The other answer saying that "the annotations do not do anything by themselves, you need to use a Validator to process the object" is correct, however, the answer lacks working instructions on how to do it using a Validator instance, which for me was what I really wanted.
Hibernate-validator is the reference implementation of such a validator. You can use it quite cleanly like this:
import static org.junit.Assert.assertFalse;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class ContactValidationTest {
private Validator validator;
#Before
public void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#Test
public void testContactSuccess() {
// I'd name the test to something like
// invalidEmailShouldFailValidation()
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
Set<ConstraintViolation<Contact>> violations = validator.validate(contact);
assertFalse(violations.isEmpty());
}
}
This assumes you have validator implementation and junit as dependencies.
Example of dependencies using Maven pom:
<dependency>
<groupId>org.hibernate</groupId>
<version>5.2.4.Final</version>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
A simple way to test validation annotations using javax:
Declare the Validator at Class level:
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Then in your test simply call it on the object you require validation on, with what exception you are validating:
Set<TheViolation<TheClassYouAreValidating> violations = validator.validate(theInstanceOfTheClassYouAreValidating);
Then simply assert the number of expected violations:
assertThat(violations.size()).isEqualTo(1);
You will need to add this to your dependencies (gradle):
compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
The annotations do not do anything by themselves, you need to use a Validator to process the object.
Your test needs to run some code like this
Configuration<?> configuration = Validation
.byDefaultProvider()
.providerResolver( new MyResolverStrategy() ) // <== this is where is gets tricky
.configure();
ValidatorFactory factory = configuration.buildValidatorFactory();
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
factory.getValidator().validate(contact); <== this normally gets run in the background by whatever framework you are using
However, the difficulty you face here are these are all interfaces, you will need implementations to be able to test. You could implement it yourself or find one to use.
However the question you want to ask yourself is what are you trying to test? That the hibernate validator works the way it should? or that your regex is correct?
If this was me I would assume that the Validator works(ie someone else tested that) and focus on the regex. Which would involve a bit of reflection
public void emailRegex(String email,boolean validates){
Field field = Contact.class.getDeclaredField("email");
javax.validation.constraints.Pattern[] annotations = field.getAnnotationsByType(javax.validation.constraints.Pattern.class);
assertEquals(email.matches(annotations[0].regexp()),validates);
}
then you can define your testMethods which are actual unit tests
#Test
public void testInvalidEmail() throws NoSuchFieldException {
emailRegex("Jackyahoo.com", false);
}
#Test
public void testValidEmail() throws NoSuchFieldException {
emailRegex("jack#yahoo.com", true);
}
#Test
public void testNoUpperCase() throws NoSuchFieldException {
emailRegex("Jack#yahoo.com", false);
}
First thanks #Eis for the answer, it helped me. It's a good way to fail the test, but I wanted a bit more "life-like" behaviour. At runtime an exception would be thrown so I came up with this:
/**
* Simulates the behaviour of bean-validation e.g. #NotNull
*/
private void validateBean(Object bean) throws AssertionError {
Optional<ConstraintViolation<Object>> violation = validator.validate(bean).stream().findFirst();
if (violation.isPresent()) {
throw new ValidationException(violation.get().getMessage());
}
}
Have an entity with validation:
#Data
public class MyEntity {
#NotBlank(message = "Name cannot be empty!")
private String name;
}
In a test you can pass an instance with invalid attributes and expect an exception:
private Validator validator;
#Before
public void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#Test(expected = ValidationException.class)
public void testValidationWhenNoNameThenThrowException() {
validateBean(new Entity.setName(""));
}
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
public class ValidationTest {
private Validator validator;
#Before
public void init() {
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
this.validator = vf.getValidator();
}
#Test
public void prereqsMet() {
Workshop validWorkshop = new Workshop(2, 2, true, 3);
Set<ConstraintViolation<Workshop>> violations = this.validator.validate(validWorkshop);
assertTrue(violations.isEmpty());
}
}
Strictly speaking it is not a unit test, rather an Integration Test. In Unit Test you would like to test the validator logic only, without any dependencies to the SPI.
https://www.adam-bien.com/roller/abien/entry/unit_integration_testing_the_bean
Here my way to unit test my objects with fields annotated with some javax.validation.constraints constraints.
I will give an example with Java 8, JPA entity, Spring Boot and JUnit 5 but the overall idea is the same whatever the context and the frameworks :
We have a nominal scenario where all fields are correctly valued and generally multiple error scenarios where one or more fields are not correctly valued.
Testing field validation is not a particularly hard thing.
But as we have many fields to validate, the tests may become more complex, we can forget some cases, introducing side effects in tests between two cases to validate or simply introduce duplication.
I will give my mind about how to avoid that.
In the OP code, we will suppose that the 3 fields have a NotNull constraint. I think that under 3 distinct constraints, the pattern and its value are less visible.
I wrote first a unit test for the nominal scenario :
import org.junit.jupiter.api.Test;
#Test
public void persist() throws Exception {
Contact contact = createValidContact();
// action
contactRepository.save(contact);
entityManager.flush();
entityManager.clear();
// assertion on the id for example
...
}
I extract the code to create a valid contact into a method as it will be helpful for no nominal cases :
private Contact createValidContact(){
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
contact.setPhone("33999999");
return contact;
}
Now I write a #parameterizedTest with as fixture source a #MethodSource method :
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import javax.validation.ConstraintViolationException;
#ParameterizedTest
#MethodSource("persist_fails_with_constraintViolation_fixture")
void persist_fails_with_constraintViolation(Contact contact ) {
assertThrows(ConstraintViolationException.class, () -> {
contactRepository.save(contact);
entityManager.flush();
});
}
To compile/run #parameterizedTest, think of adding the required dependency that is not included in the junit-jupiter-api dependency :
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
In the fixture method to create invalid contacts, the idea is simple. For each case, I create a new valid contact object and I set incorrectly only the field to validate concerned to.
In this way, I ensure that no side effect between cases are present and that each case provokes itself the expected validation exception as without the field set the valid contact was successful persisted.
private static Stream<Contact> persist_fails_with_constraintViolation_fixture() {
Contact contactWithNullName = createValidContact();
contactWithNullName.setName(null);
Contact contactWithNullEmail = createValidContact();
contactWithNullEmail.setEmail(null);
Contact contactWithNullPhone = createValidContact();
contactWithNullPhone.setPhone(null);
return Stream.of(contactWithNullName, contactWithNullEmail, contactWithNullPhone);
}
Here is the full test code :
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import javax.validation.ConstraintViolationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#DataJpaTest
#ExtendWith(SpringExtension.class)
public class ContactRepositoryTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private ContactRepository contactRepository;
#BeforeEach
public void setup() {
entityManager.clear();
}
#Test
public void persist() throws Exception {
Contact contact = createValidContact();
// action
contactRepository.save(contact);
entityManager.flush();
entityManager.clear();
// assertion on the id for example
...
}
#ParameterizedTest
#MethodSource("persist_fails_with_constraintViolation_fixture")
void persist_fails_with_constraintViolation(Contact contact ) {
assertThrows(ConstraintViolationException.class, () -> {
contactRepository.save(contact);
entityManager.flush();
});
}
private static Stream<Contact> persist_fails_with_constraintViolation_fixture() {
Contact contactWithNullName = createValidContact();
contactWithNullName.setName(null);
Contact contactWithNullEmail = createValidContact();
contactWithNullEmail.setEmail(null);
Contact contactWithNullPhone = createValidContact();
contactWithNullPhone.setPhone(null);
return Stream.of(contactWithNullName, contactWithNullEmail, contactWithNullPhone);
}
}
There are 2 things that you need to check:
The validation rules are configured correctly
The validation rules can be checked the way others advise - by creating a validator object and invoking it manually:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator()
Set violations = validator.validate(contact);
assertFalse(violations.isEmpty());
With this you should check all the possible cases - there could be dozens of them (and in this case there should be dozens of them).
The validation is triggered by the frameworks
In your case you check it with Hibernate, therefore there should be a test that initializes it and triggers some Hibernate operations. Note that for this you need to check only one failing rule for one single field - this will be enough. You don't need to check all the rules from again. Example could be:
#Test(expected = ConstraintViolationException.class)
public void validationIsInvokedBeforeSavingContact() {
Contact contact = Contact.random();
contact.setEmail(invalidEmail());
contactsDao.save(contact)
session.flush(); // or entityManager.flush();
}
NB: don't forget to trigger flush(). If you work with UUIDs or sequences as an ID generation strategy, then INSERT is not going to be flushed when you save() - it's going to be postponed until later.
This all is a part of how to build a Test Pyramid - you can find more details here.
such as:
public class Test {
#Autowired
private Validator validator;
public void testContactSuccess() {
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
System.err.println(contact);
Set<ConstraintViolation<Contact>> violations = validator.validate(contact);
assertTrue(violations.isEmpty());
}
}
and you also need add bean autowired in your context.xml, such as:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
</bean>
If you try using new versions of the validator but land on that thread (like me), you will start getting tons of wired exceptions. So should have in mind that to do test with Hibernate 7+
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.2.Final</version>
<scope>test</scope>
</dependency>
should be sure that you are NOT using
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
but switched to
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.1</version>
</dependency>
and have
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
<scope>test</scope>
</dependency>
For those with Spring Boot with Spring-Data-JPA you just need to autowire the validator
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.validation.Validator;
import static org.assertj.core.api.Assertions.assertThat;
#SpringBootTest
class ValidatorTest {
#Autowired
private Validator validator;
#Test
void ensureValidatorIsLoaded() {
assertThat(validator).isNotNull();
}
#Test
void failValidate() {
final var violations = validator.validate(new StartRequest());
assertThat(violations).isNotEmpty();
}
#Test
void passValidate() {
final var startRequest = StartRequest.builder()
.contentType("foo/bar")
.contentMd5Hash("abcdef1234567890abcdef1234567890")
.category("Pc")
.contentLength(55)
.siteId("ca1")
.desiredExpiration(55)
.build();
final var violations = validator.validate(startRequest);
assertThat(violations).isEmpty();
}
}
I think validations would work after calling predefined methods which is usually done by the containers mostly not immediately after calling setters of the object. From the documentation link you shared:
>
By default, the Persistence provider will automatically perform validation on entities with persistent fields or properties annotated with Bean Validation constraints immediately after the PrePersist, PreUpdate, and PreRemove lifecycle events.
I am using PowerMock easy mock to mock static method of a class. I have two test cases written, which if I run independently run fine, but give me an error while running simultaneously.
CarTest:
#RunWith(PowerMockRunner.class)
#PrepareForTest({ ServiceCaller.class })
public class CarTest {
ServiceCaller mockServiceCallerObjectToReturn;
public CarTest() {
PowerMock.mockStaticPartial(ServiceCaller.class, "getInstance");
mockServiceCallerObjectToReturn = PowerMock.createMock(ServiceCaller.class);
EasyMock.expect(ServiceCaller.getInstance()).andReturn(mockServiceCallerObjectToReturn);
}
#Test
public void test1() throws IOException {
PowerMock.reset(mockServiceCallerObjectToReturn);
PowerMock.reset(ServiceCaller.class);
EasyMock.expect(ServiceCaller.getInstance()).andReturn(mockServiceCallerObjectToReturn);
EasyMock.expect(mockServiceCallerObjectToReturn.checkValidity("testDriver")).andReturn(false);
PowerMock.replay(mockServiceCallerObjectToReturn);
PowerMock.replay(ServiceCaller.class);
Car car = CarFactory.getInstance().getCar();
boolean canDrive = car.drive("testDriver");
Assert.assertEquals(canDrive, false);
PowerMock.verify(mockServiceCallerObjectToReturn);
PowerMock.verify(ServiceCaller.class);
}
#Test
public void test2() throws IOException {
PowerMock.reset(mockServiceCallerObjectToReturn);
PowerMock.reset(ServiceCaller.class);
EasyMock.expect(ServiceCaller.getInstance()).andReturn(mockServiceCallerObjectToReturn);
EasyMock.expect(mockServiceCallerObjectToReturn.checkValidity("testDriver")).andReturn(false);
PowerMock.replay(mockServiceCallerObjectToReturn);
PowerMock.replay(ServiceCaller.class);
Car car = CarFactory.getInstance().getCar();
boolean canDrive = car.drive("testDriver");
Assert.assertEquals(canDrive, false);
PowerMock.verify(mockServiceCallerObjectToReturn);
PowerMock.verify(ServiceCaller.class);
}
}
CarFactory:
public class CarFactory {
private static final String CAR_SPRING_CONTEXT_XML = "/com/archit/mock/spring-config/CarSpringContext.xml";
protected static final ApplicationContext CONTEXT = new ClassPathXmlApplicationContext(new String[] { CAR_SPRING_CONTEXT_XML });
private static final CarFactory INSTANCE = new CarFactory();
public static CarFactory getInstance() {
return INSTANCE;
}
public Car getCar() {
return CONTEXT.getBean("car", Car.class);
}
}
Car:
package com.archit.mock;
public class Car {
private final ServiceCaller serviceCaller;
public Car(final ServiceCallerFactory serviceCallerFactory) {
this.serviceCaller = serviceCallerFactory.getServiceCaller();
}
public boolean drive(final String driver) {
return (serviceCaller.checkValidity(driver));
}
}
ServiceCaller:
package com.archit.mock;
public class ServiceCaller {
private static class ServiceCallerHolder {
private static ServiceCaller INSTANCE = new ServiceCaller();
}
public static ServiceCaller getInstance() {
return ServiceCallerHolder.INSTANCE;
}
public boolean checkValidity(final String x) {
// Do some call
throw new IllegalStateException("This should have been mocked");
}
}
ServiceCallerFactory:
package com.archit.mock;
public class ServiceCallerFactory {
public ServiceCaller getServiceCaller() {
return ServiceCaller.getInstance();
}
}
Spring Config:
<bean name="car" class="com.archit.mock.Car">
<constructor-arg>
<ref bean="serviceCallerFactory" />
</constructor-arg>
</bean>
<bean name="serviceCallerFactory" class="com.archit.mock.ServiceCallerFactory" />
Error:
java.lang.AssertionError:
Unexpected method call ServiceCaller.checkValidity("testDriver"):
ServiceCaller.checkValidity("testDriver"): expected: 1, actual: 2
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:44)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:85)
at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:94)
at com.archit.mock.ServiceCaller$$EnhancerByCGLIB$$9848ad9e.checkValidity(<generated>)
at com.archit.mock.Car.drive(Car.java:12)
at com.archit.mock.CarTest.test2(CarTest.java:60)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:66)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:312)
at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:86)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:94)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:296)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:284)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:84)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:49)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:209)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:148)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:122)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:120)
at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:102)
at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:42)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Other Observations:
Making the object scope as prototype in spring config works fine.
Both tests when run individually work fine.
Based on above 2, It seems to be a problem with re-setting the mocks.
I've found it easier with mocks to have them start as new objects each time the tests are run. You can achieve this by having all your mock objects at the top as private variables, using the Mockito.Mock annotation:
#Mock
private MockOneClass mockOne;
...
#Mock
private MockNClass mockN;
Then using the JUnit Before annotation, create a setup function of some sort that initializes all the mock objects:
#Before
public void setup() {
// initialize all the #Mock objects
MockitoAnnotations.initMocks(this);
}
This way, before each test runs you get a new Mock created, which you can then apply any amount of expects and functionality to, without having to worry about clearing out any old mocking done in a previous test. If you have any Mock that you know will provide some specific functionality (static singleton mock getInstance calls for example) can be called in this setup function to help keep the tests cleaner of Mock resetting.
I have some test that has 10+ tests all run in a row that uses this framework. Not only does it make it a lot easier to read, but it makes setting up tests from scratch really fast. Being able to copy old mock setups in Tests and remove/change certain pieces is so much easier then having to upkeep every mock object every test, which just doesn't extend well once you start getting more tests.