I'm new to Spock and I have this class that I want to unit test. In the class there's a method that validates a product. To pass the validation the product must have fullPrice and this must contain all the other prices, otherwise an exception should be thrown.
class PriceValidator {
private final Logger logger = Logger.getLogger(MyService.class)
void validate (Product product) throws SubsystemException {
if (!product.fullPrice || !product.fullPrice.priceInclVAT || !product.fullPrice.priceExclVAT || !product.fullPrice.vat) {
String message = "No price found!"
logger.error(message)
throw new SubsystemException(
Subsystem.MySystem,
FailureCause.NO_PRICE_FOUND,
message
)
}
}
}
I have tried testing this in several ways without any luck. I'm guessing I need mocks, but that is also new to me. This is an example of a test that I have tried, resulting in "Test framework quit unexpectedly" (and all the prices are Strings):
class PriceValidatorTest extends Specification {
#Unroll
def "No price should throw an exception"() {
given:
PriceValidator priceValidator = new PriceValidator()
Product product = Mock()
when:
product.fullPrice != null
product.fullPrice.priceInclVAT = "100"
product.fullPrice.priceExclVAT = "70"
product.fullPrice.vat = null
priceValidator.validate(product)
then:
thrown(SubsystemException)
}
}
Anyone having a suggestion how to test the PriceValidator?
You need to test several cases, one of them would be:
def "No price should throw an exception"() {
given:
PriceValidator priceValidator = new PriceValidator()
Product product = Mock() {
getFullPrice() >> null
}
when:
priceValidator.validate(product)
then:
thrown(SubsystemException)
}
What You need to to is to mock the behavior of Product class (the line with >> rightShift operator). No it seems ready to be tested. Other scenarios, when price is filled should be tested in separate method. Any further questions?
Related
I wanted to update a nested list but I experience a strange behavior where I have to call method twice to get it done...
Here is my POJO:
#Document(collection = "company")
data class Company (
val id: ObjectId,
#Indexed(unique=true)
val name: String,
val customers: MutableList<Customer> = mutableListOf()
//other fields
)
Below is my function from custom repository to do the job which I based on this tutorial
override fun addCustomer(customer: Customer): Mono<Company> {
val query = Query(Criteria.where("employees.keycloakId").`is`(customer.createdBy))
val update = Update().addToSet("customers", customer)
val upsertOption = FindAndModifyOptions.options().upsert(true)
//if I uncomment below this will work...
//mongoTemplate.findAndModify(query, update, upsertOption, Company::class.java).block()
return mongoTemplate.findAndModify(query, update, upsertOption, Company::class.java)
}
In order to actually add this customer I have to either uncomment the block call above or call the method two times in the debugger while running integration tests which is quite confusing to me
Here is the failing test
#Test
fun addCustomer() {
//given
val company = fixture.company
val initialCustomerSize = company.customers.size
companyRepository.save(company).block()
val customerToAdd = CustomerReference(id = ObjectId.get(),
keycloakId = "dummy",
username = "customerName",
email = "email",
createdBy = company.employees[0].keycloakId)
//when, then
StepVerifier.create(companyCustomRepositoryImpl.addCustomer(customerToAdd))
.assertNext { updatedCompany -> assertThat(updatedCompany.customers).hasSize(initialCustomerSize + 1) }
.verifyComplete()
}
java.lang.AssertionError:
Expected size:<3> but was:<2> in:
I found out the issue.
By default mongo returns entity with state of before update. To override it I had to add:
val upsertOption = FindAndModifyOptions.options()
.returnNew(true)
.upsert(true)
In data class I defined the 'name' must be unique across whole mongo collection:
#Document
data class Inn(#Indexed(unique = true) val name: String,
val description: String) {
#Id
var id: String = UUID.randomUUID().toString()
var intro: String = ""
}
So in service I have to capture the unexpected exception if someone pass the same name again.
#Service
class InnService(val repository: InnRepository) {
fun create(inn: Mono<Inn>): Mono<Inn> =
repository
.create(inn)
.onErrorMap(
DuplicateKeyException::class.java,
{ err -> InnAlreadyExistedException("The inn already existed", err) }
)
}
This is OK, but what if I want to add more info to the exceptional message like "The inn named '$it.name' already existed", what should I do for transforming exception with enriched message.
Clearly, assign Mono<Inn> to a local variable at the beginning is not a good idea...
Similar situation in handler, I'd like to give client more info which derived from the customized exception, but no proper way can be found.
#Component
class InnHandler(val innService: InnService) {
fun create(req: ServerRequest): Mono<ServerResponse> {
return innService
.create(req.bodyToMono<Inn>())
.flatMap {
created(URI.create("/api/inns/${it.id}"))
.contentType(MediaType.APPLICATION_JSON_UTF8).body(it.toMono())
}
.onErrorReturn(
InnAlreadyExistedException::class.java,
badRequest().body(mapOf("code" to "SF400", "message" to t.message).toMono()).block()
)
}
}
In reactor, you aren't going to have the value you want handed to you in onErrorMap as an argument, you just get the Throwable. However, in Kotlin you can reach outside the scope of the error handler and just refer to inn directly. You don't need to change much:
fun create(inn: Mono<Inn>): Mono<Inn> =
repository
.create(inn)
.onErrorMap(
DuplicateKeyException::class.java,
{ InnAlreadyExistedException("The inn ${inn.name} already existed", it) }
)
}
I'm currently trying to create a test webhook-notification as it's shown in the documentation:
HashMap<String, String> sampleNotification = gateway.webhookTesting().sampleNotification(
WebhookNotification.Kind.SUBSCRIPTION_WENT_PAST_DUE, "my_id"
);
WebhookNotification webhookNotification = gateway.webhookNotification().parse(
sampleNotification.get("bt_signature"),
sampleNotification.get("bt_payload")
);
webhookNotification.getSubscription().getId();
// "my_id"
First off I don't know what my_id actually should be. Is it supposed to be a plan ID? Or should it be a Subscription ID?
I've tested all of it. I've set it to an existing billing plan in my vault and I also tried to create a Customer down to an actual Subscription like this:
public class WebhookChargedSuccessfullyLocal {
private final static BraintreeGateway BT;
static {
String btConfig = "C:\\workspaces\\mz\\mz-server\\mz-web-server\\src\\main\\assembly\\dev\\braintree.properties";
Braintree.initialize(btConfig);
BT = Braintree.instance();
}
public static void main(String[] args) {
WebhookChargedSuccessfullyLocal webhookChargedSuccessfullyLocal = new WebhookChargedSuccessfullyLocal();
webhookChargedSuccessfullyLocal.post();
}
/**
*
*/
public void post() {
CustomerRequest customerRequest = new CustomerRequest()
.firstName("Testuser")
.lastName("Tester");
Result<Customer> createUserResult = BT.customer().create(customerRequest);
if(createUserResult.isSuccess() == false) {
System.err.println("Could not create customer");
System.exit(1);
}
Customer customer = createUserResult.getTarget();
PaymentMethodRequest paymentMethodRequest = new PaymentMethodRequest()
.customerId(customer.getId())
.paymentMethodNonce("fake-valid-visa-nonce");
Result<? extends PaymentMethod> createPaymentMethodResult = BT.paymentMethod().create(paymentMethodRequest);
if(createPaymentMethodResult.isSuccess() == false) {
System.err.println("Could not create payment method");
System.exit(1);
}
if(!(createPaymentMethodResult.getTarget() instanceof CreditCard)) {
System.err.println("Unexpected error. Result is not a credit card.");
System.exit(1);
}
CreditCard creditCard = (CreditCard) createPaymentMethodResult.getTarget();
SubscriptionRequest subscriptionRequest = new SubscriptionRequest()
.paymentMethodToken(creditCard.getToken())
.planId("mmb2");
Result<Subscription> createSubscriptionResult = BT.subscription().create(subscriptionRequest);
if(createSubscriptionResult.isSuccess() == false) {
System.err.println("Could not create subscription");
System.exit(1);
}
Subscription subscription = createSubscriptionResult.getTarget();
HashMap<String, String> sampleNotification = BT.webhookTesting()
.sampleNotification(WebhookNotification.Kind.SUBSCRIPTION_CHARGED_SUCCESSFULLY, subscription.getId());
WebhookNotification webhookNotification = BT.webhookNotification()
.parse(
sampleNotification.get("bt_signature"),
sampleNotification.get("bt_payload")
);
System.out.println(webhookNotification.getSubscription().getId());
}
}
but all I'm getting is a WebhookNotification instance that has nothing set. Only its ID and the timestamp appears to be set but that's it.
What I expected:
I expected to receive a Subscription object that tells me which customer has subscribed to it as well as e.g. all add-ons which are included in the billing plan.
Is there a way to get such test-notifications in the sandbox mode?
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact support.
webhookNotification.getSubscription().getId(); will return the ID of the subscription associated with sampleNotification, which can be anything for testing purposes, but will be a SubscriptionID in a production environment.
Receiving a dummy object from webhookTesting().sampleNotification() is the expected behavior, and is in place to help you ensure that all kinds of webhooks can be correctly caught. Once that logic is in place, in the Sandbox Gateway under Settings > Webhooks you can specify your endpoint to receive real webhook notifications.
In the case of SUBSCRIPTION_CHARGED_SUCCESSFULLY you will indeed receive a Subscription object containing add-on information as well as an array of Transaction objects containing customer information.
I've read a lot about uniqueness and constraints in Grails (but maybe not enough)
I can't make the unique constraint to work on multiple fields as explained here:
http://grails.org/doc/1.3.7/ref/Constraints/unique.html
(I'm using grails 1.3.9)
I have 2 domain classes:
class Dog {
static constraints = {
humanSsn(unique: ['name', 'breed'])
//I also tried with just 2 fields, didn't work either.
}
Integer humanSsn
String name
String breed
}
class Human {
static constraints = {
ssn(unique: true)
}
Integer ssn
String name
}
It is a legacy DB, so I cant modify the tables.
When I save a Human, I (just to test) save two dogs with the same name, breed and humanSsn
def humanoInstance = new Humano(params)
if (humanoInstance.save(flush: true)) {
def newDog = new Dog()
def newDogTwo = new Dog()
newDog.name = "n1"
newDog.breed = "b1"
newDog.humanSsn = humanInstance.ssn
println newDog.validate()
println newDog.getErrors()
newDog.save(failOnError:true)
newDogTwo.name = "n1"
newDogTwo.breed = "b1"
newDogTwo.humanSsn = humanInstance.ssn
println newDogTwo.validate()
println newDogTwo.getErrors()
newDogTwo.save(failOnError:true)
}
But it saves anyway the 2 dogs without complaining nor throwing any errors.
true
org.springframework.validation.BeanPropertyBindingResult: 0 error
true
org.springframework.validation.BeanPropertyBindingResult: 0 error
What am I doing wrong?
Thanks in advance.
it may be due to validation works on database level
and newDog.save(failOnError:true) doesnot save dog object immediately
have you try
newDog.save(flush:true)
for first dog and then
newDogTwo.save(failOnError:true)
it should work
I have the following problem:
I have a domain class called customer, with a field discount embedded.
class Customer {
...
String username
Discount discount
static constraints = { ... }
}
class Discount {
Integer item1
Integer item2
static constraints = { item1 min:1, max:100, nullable:true }
}
I have a controller, where a customer's data can be modified. The code goes something like this:
def edit() {
Customer c = Customer.findByUsername(params.userName)
if(request.method != 'GET'){
bindData(c, params)
if(c.validate()) {
//save the result
}
}
println c.dump()//1
model:[customer:c]
}
Then in the edit.gsp I put the following code:
${customer.dump()}//2
${customer.discount.dump()}
Now my problem is, if I have a validation error, for example the user enters 123 for item1, I get the appropriate errors object which says that Customer bean has 1 field error on field discount.item1 when I call println c.dump()//1
In the edit.gsp on the other hand, the customer bean doesn't have any field errors but customer.discount has the mentioned error. This is a big inconvenience, because I want to render errors next to the fields like so:
<g:renderErrors bean="${customer}" field="discount.item1"/>
But the customer bean doesn't have any errors, just the discount bean (therefore I don't get any errors rendered).
Has this problem occured to any of you ?
It seems that #Validatable classes use functionality of spring framework's AbstractBindingResult, which doesn't support this usage (as far as I can tell).
However I was able to create a workaround, which could be used as a taglib to achieve the same effect:
Given two validateable classes
#Validateable
class TestA {
TestB b
static constraints = {
b validator: {
it.validate()
}
}
}
#Validateable
class TestB {
int c
static constraints = {
c min: 100
}
}
One can simply resolve the bean and the attribute and delegate the rendering to the existing taglib.
import test.*
import org.codehaus.groovy.grails.plugins.web.taglib.*
ValidationTagLib validationTagLib = ctx.getBean(ValidationTagLib)
renderErrorsWithNestedFields = { attrs, body ->
def modifiedAttrs = attrs
String[] path = attrs?.field?.split('\\.')
if(path?.size() > 1) {
Object resolvedBean
resolvedBean = attrs.bean
path[0..-2].each {
//Some error handling would not hurt, but this is just a proof of concept
resolvedBean = resolvedBean[it]
}
modifiedAttrs = new HashMap(attrs)
modifiedAttrs.bean = resolvedBean
modifiedAttrs.field = path.last()
}
validationTagLib.renderErrors (modifiedAttrs, null)
}
TestA a = new TestA(b:new TestB(c:3))
a.validate()
renderErrorsWithNestedFields([bean:a, field:'b.c'], null)
And this should render an error for violating the min constraint on c.