Parametrized Spock test using #WithMockUser - unit-testing

I'd like to run a parametrized test in Spock using the #WithMockUser with a different role per each iteration.
As an example, the following test code shows no compilation error and is run twice.
But the result fails, since the #role is resolved only in the #Unroll, but not in the #WithMockUser annotation.
#Unroll("#role is authorized to get admin")
#WithMockUser(username = "user", roles = ["#role"])
def "User is authorized to get admin"() {
when:
def adminDto = adminController.getAdmin()
then:
adminDto.getStatusCode() == HttpStatus.OK
where:
role << ["USER", "ADMIN"]
}
So my question is: Is it possible to run such a test as a parametrized one?
Thank you in advance!
Note: I'm aware the annotation could look like:
#WithMockUser(username = "user", roles = ["USER", "ADMIN"])
but this would be a different behavior - one call with both roles.

Short answer no.
The annotations are statically compiled at compile time and cannot be modified by Spock, the iterations are generated/evaluated dynamically at runtime.
The #Unroll annotation has some special support for data variables to make it work, while the Spring annotation doesn't know anything about Spock.
You could look at WithMockUserSecurityContextFactory and use that to manually set the the SecurityContext in the given block.

Related

Spock test in GEB cookies using where clause

I've configured some geb tests to check a different messages depending on the login attempt in my web app. Since the message and the input fields will change at the third login attempt.
The login is a two step login based on password send to a specific phone number, so in the first page LoginPage the user introduces their Id and phoneNumber, then it's redirected to the second page ValidationLoginPage where the user introduces the received password.
I want to check that in the second page the user can only introduces three bad passwords and at fourth attempt the input to introduce a password will disappear and a different message indicating that there are no more attempts it's showed.
To check this I prepared a test which introduces the Id and phoneNumber at the given: clause, and using where: clause it introduces a bad password three times. Since where: repeat all test I try to control the part to repeat using injected variable like in where: so I've something like:
def "Test max loging attempts"(){
given:
if(loginAttempt == 1)
to LoginPage
loginModule.startLogin(cfg.user.id,cfg.user.phone)
}
when:
at LoginValidationPage
assert $('div.box_id_header h3').text() == 'Verify your code'
assert $('#code').css('display').contains('block')
loginModule.verifyPassword('WRONGPASSWORD')
then:
at LoginValidationPage
println "Attempt ${loginAttempt}"
if(loginAttempt == 4){
// last attempt
assert $('#code').css('display') == 'none'
assert $('#divCodeErrorMsg').text().contains('No more attempts')
}else{
assert $('#code').css('display').contains('block')
assert $('#divCodeErrorMsg').text().contains('Wrong password. Try again.')
}
where:
loginAttempt << (1..4)
}
My problem is, that cookies are cleared for each where: iteration, thought the message and the behavior is not which I expect. I don't want to configure autoClearCookies=false in GebConfig.groovy file since I've another tests where this feature is necessary. There is a way to avoid the clear cookies for this method using spock def setupSpec() {} method and reactivate in def cleanupSpec() {} method?
Additionally it's also possible to use where: in a cleaner way avoiding to check the loginAttempt variable to avoid run given: part multiple times, or there is a better approach not using where: at all?
The problem is that you maybe misunderstand and thus abuse Spock's where: block. It is designed to parametrise a feature method and each run of that method comprises an independent feature. If you #Unroll your feature, even for each set of where: parameters a new method is generated. Because features should be independent of each other and theoretically be able run run in any given order or even in parallel, test fixtures need to be reset in order to run them. This is what happens with your cookies because you abuse the where: feature to implement a simple loop.
There are other little problems in your code, such as the non-asserted at check in the when: block. Even if at yields false, it has no consequence there if you do not use assert. You can only skip assert in then: or expect: blocks, but not in given: or when: and also not inside closures or helper methods which I will both use in my sample code.
How about this?
package de.scrum_master.stackoverflow.foo
import geb.spock.GebReportingSpec
class LoginTest extends GebReportingSpec {
def loginModule = new LoginModule()
def cfg = new Config()
def "Test max login attempts"() {
given: "we are at the login page"
browser.config.autoClearCookies = false
to LoginPage
when: "logging in 4x with wrong credentials"
(1..4).each {
loginWithWrongCredentials(it, it < 4)
}
then: "we get a 'no more attempts' error message"
$('#codi').css('display') == 'none'
$('#divCodiValidacioError').text().contains('No more attempts')
}
def loginWithWrongCredentials(int loginAttempt, boolean checkForWrongPasswordMessage) {
println "Login attempt ${loginAttempt}"
loginModule.startLogin(cfg.user.id, cfg.user.phone)
assert at(LoginValidationPage)
assert $('div.box_id_header h3').text() == 'Verify your code'
assert $('#code').css('display').contains('block')
loginModule.verifyPassword('WRONGPASSWORD')
assert at(LoginValidationPage)
if (checkForWrongPasswordMessage) {
assert $('#codi').css('display').contains('block')
assert $('#divCodiErrorMsg').text().contains('Wrong password. Try again.')
}
}
}
I would also recommend to move the content asserts from loginWithWrongCredentials into helper methods of LoginValidationPage where they rather belong and just call them from the test.
Finally disabling the autoClearCookies in the setup step I get the desired behavior.
def setup() {
browser.getConfig().setAutoClearCookies(false)
}
However surprisingly if I use setupSpec() method, seems that it not works, since only first where: iteration has autoClearCookies=false and the rest has autoClearCookies=true.

request issue when unit-testing webapp2 with mock and patch

I'm building unit tests for this webapp2 handler (built for GAE)
class PushNotificationHandler(webapp2.RequestHandler):
def post(self):
UserNotification.parse_from_queue(self.request)
return
app = webapp2.WSGIApplication([
(r'/push/notification', PushNotificationHandler),
], debug=True)
One test is
#patch.object(UserNotification, 'parse_from_queue')
def test_post_webapp(self, p_parse_from_queue):
response = webtest.TestApp(app).post('/push/notification')
eq_(response.status_int, 200)
p_parse_from_queue.assert_called_once_with(response.request)
The HTTP reply is OK, but the mock assertion fails:
Expected call: parse_from_queue(<TestRequest at 0x105a89850 POST http://localhost/push/notification>)
Actual call: parse_from_queue(<Request at 0x105a89950 POST http://localhost/push/notification>)
I can't understand why the request is not the correct one (looks like a deep copy). Is there anything wrong with the unit-test, or is that the way webapp2 handle requests. In the second case, is there a way to test it, without creating a separate test to test PushNotificationHandler.post()
Thanks
I've used mock's call_args in a similar situation. You can do something like this:
request = p_parse_from_queue.call_args[0][0]
self.assertEqual(request.url, "foo")
self.assertEqual(request.*, *)
The [0][0] gives you the first passed argument assuming that you are using ordered arguments and not keyword arguments.
You can then proceed to check other relevant attributes of the request object to make sure it is behaving as expected.

Why is Digest::SHA1 preventing proper annotation of a model?

I am using annotate in my app and all models are successfully annotated except for user.rb, which shows the following error when I annotate:
Unable to annotate user.rb: wrong number of arguments (0 for 1)
Outside of annotating, everything else works fine. User creation, updating, deletion, login, sign out, it all works properly. I have determined that the problem is with the Digest::SHA1, which I use to create session tokens, as demonstrated below in the snippet from user.rb.
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.hash(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
remember_token = User.hash(User.new_remember_token)
end
If I remove the second (def User.hash(token)) and instead do the following:
def User.new_remember_token
SecureRandom.urlsafe_base64
end
private
def create_remember_token
remember_token = Digest::SHA1.hexdigest(User.new_remember_token.to_s)
end
then annotate is happy and successfully annotates user.rb. However, this isn't really the ruby way as my session helper utilizes that User.hash(token) call several times. What am I not understanding about Digest::SHA1.hexdigest or the way that I am utilizing it?
Looks like you're working through The Rails Tutorial.
The likely reason you're seeing issues with your User.hash method is nothing to do with Digest::SHA1, but is because the method itself is inadvertently overriding Ruby's Object#hash method, which is giving you some cryptic errors. Link to Github issue about it.
So, like this commit to the Sample App repository, rename all your instances of User.hash to User.digest and hopefully that should fix your errors.

Can I use "Where" block and helper methods at the same time in spock

I have a specification which essentially looks like this:
def "my example specification"(){
given:"some mocked object which depends on a object created in the where clause"
def display = mockDisplay()
and:"a activityt"
def activity = new ConfigActivity(display)
when:
activity.doStuff()
then:
1 * display.select()
where:
dependency << new Dependency()
}
private mockDisplay() {
def display = Mock(ConfigActivity.Display)
display.addDependency(dependency)
return display
}
I understand that that the "mockDisplay()" method is out of scope from the "where" clause. However since a few specifications tend to get rather cluttered with boilerplate code (not in this over simplified example offcourse) I really need some way to reuse my "given" statements and in doing so I would really like to make use of the Spock "where" clauses as well.
Is this doable? Or is there another way to go about this problem?
There is no magic way to access a data variable from a helper method, but you can pass it as a method parameter.

How to avoid MIXED_DML_OPERATION error in Salesforce tests that create Users

Sometimes in Salesforce tests you need to create User objects to run part of the test as a speciifc type of user.
However since the Salesforce Summer 08 update, attempts to create both User objects and normal objects (such as Accounts) in the same test lead to the following error:
MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): User, original object: Account
Note that the error doesn't happen when you run the tests from Eclipse/Force.com IDE, but it does happen when you deploy to Salesforce and then run the tests from within Salesforce.
How do I re-write my tests to avoid this error?
Here's a simple example of a test that causes the error:
static testMethod void test_mixed_dmlbug() {
Profile p = [select id from profile where name='(some profile)'];
UserRole r = [Select id from userrole where name='(some role)'];
User u = new User(alias = 'standt', email='standarduser#testorg.com',
emailencodingkey='UTF-8', lastname='Testing',
languagelocalekey='en_US',
localesidkey='en_US', profileid = p.Id, userroleid = r.Id,
timezonesidkey='America/Los_Angeles',
username='standarduser#testorg.com');
Account a = new Account(Firstname='Terry', Lastname='Testperson');
insert a;
System.runAs(u) {
a.PersonEmail = 'test#madeupaddress.com';
update a;
}
}
Not many Salesforce people on here yet, I guess.
I found a solution, I don't know why it works, but it works.
All parts of the test that access normal objects need to be wrapped in a System.runAs that explicitly uses the current user, like this:
User thisUser = [ select Id from User where Id = :UserInfo.getUserId() ];
System.runAs ( thisUser ) {
// put test setup code in here
}
So, the example text_mixed_dmlbug method given in the question, would become:
static testMethod void test_mixed_dmlbug() {
User u;
Account a;
User thisUser = [ select Id from User where Id = :UserInfo.getUserId() ];
System.runAs ( thisUser ) {
Profile p = [select id from profile where name='(some profile)'];
UserRole r = [Select id from userrole where name='(some role)'];
u = new User(alias = 'standt', email='standarduser#testorg.com',
emailencodingkey='UTF-8', lastname='Testing',
languagelocalekey='en_US',
localesidkey='en_US', profileid = p.Id, userroleid = r.Id,
timezonesidkey='America/Los_Angeles',
username='standarduser#testorg.com');
a = new Account(Firstname='Terry', Lastname='Testperson');
insert a;
}
System.runAs(u) {
a.PersonEmail = 'test#madeupaddress.com';
update a;
}
}
Then the MIXED_DML_OPERATION errors stop happening.
It seems like you've found a workaround. I just wanted to try and clear up why you where getting this error.
I think you are running into this issue (per http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dml_non_mix_sobjects.htm):
sObjects That Cannot Be Used Together in DML Operations
Some sObjects require that you perform DML operations on only one type per transaction. For example, you cannot insert an account, then insert a user or a group member in a single transaction. The following sObjects cannot be used together in a transaction:
* Group1
* GroupMember
* QueueSObject
* User2
* UserRole
* UserTerritory
* Territory
Important The primary exception to
this is when you are using the runAs
method in a test.
In addition, the Summer 08 Release notes (that link is a PDF) say:
In previous releases, in a single
transaction that involved triggers,
you could perform DML operations on
more than one type of sObject, for
example, you could insert an account,
then insert a user. As of Summer
'08, you can only perform DML
operations on a single type of sObject
from the following list of sObjects.
For example, you cannot insert an
account, then insert a user, or update
a group, then insert a group
member.
Group
GroupMember
QueueSObject
User
UserRole
UserTerritory
Territory
In addition, User and Territory now
support the insert and update DML
operations, and UserRole
now supports the insert, update delete
and upsert DML operations.
Apex DML operations are not supported
on the following sObjects:
AccountTerritoryAssignmentRule
AccountTerritoryAssignmentRuleItem
UserAccountTeamMember
This behavior is actually documented in the salesforce documentation: http://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#StartTopic=Content/apex_dml_non_mix_sobjects.htm?SearchType. Read where it say "Important
The primary exception to this is when you are using the runAs method in a test"
Just found this in the documentation:
Other Uses of runAs
You can also use the runAs method to perform mixed DML operations in your test by enclosing the DML operations within the runAs block. In this way, you bypass the mixed DML error that is otherwise returned when inserting or updating setup objects together with other sObjects. See sObjects That Cannot Be Used Together in DML Operations.
So it looks like the RunAs workaround is not a workaround but is assumed by Salesforce as the only way of going by the mixed DML issue.
Hope this helps
Reference
This error is so common when attempting to create user and other objects records in a single transaction in apex.
Workaround in apex class/trigger : use future method for creating user when encountered the error
Workaround in test class : don't try creating a new user data, instead use ))>
code-snippet at -
https://thesalesforcedev.blogspot.com/2019/07/mixeddmloperation-dml-operation-on.html