Spring MVC 4.2 : How to Unit Test Controller with #RequestPart Params - unit-testing

I have a requestMapping of the form:
#RequestMapping(
value = "/submitCase",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
method = RequestMethod.POST
)
public Object submitCase(
#RequestPart(name = "attachment[0]", required = false) MultipartFile attachment1,
#RequestPart(name = "attachment[1]", required = false) MultipartFile attachment2,
#RequestPart(name = "attachment[2]", required = false) MultipartFile attachment3,
#RequestPart(name = "attachment[3]", required = false) MultipartFile attachment4,
#RequestPart(name = "attachment[4]", required = false) MultipartFile attachment5,
#RequestPart(name = "caseDetails") CaseDetails caseDetails) {}
Now I want to write a test for this with MockMvcBuilders. However I am unable to do so.
The challenge here is that the request handler consumes multipart/form-data, which consists of 4 Multipart Files, and 1 Json data.
Any ideas on how to solve this? Please bear in mind i am constrained to use Spring 4.3.
Please let me know if you require any more information.

Have a look at the great example here: https://stackoverflow.com/a/21805186/3976662
Note, that MockMvcRequestBuilders.html#multipart used in the example is not available in Spring 4.3.0, yet. Use MockMvcRequestBuilders.html#fileUpload instead (deprecated in Spring 5).
CaseDetails.java:
public class CaseDetails {
private String exampleAttr;
public String getExampleAttr() {
return exampleAttr;
}
public void setExampleAttr(String exampleAttr) {
this.exampleAttr = exampleAttr;
}
}
UploadController.java:
#Controller
public class UploadController {
#RequestMapping(
value = "/submitCase",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
method = RequestMethod.POST
)
#ResponseBody
public Object submitCase(
#RequestPart(name = "attachment[0]", required = false) MultipartFile attachment1,
#RequestPart(name = "attachment[1]", required = false) MultipartFile attachment2,
#RequestPart(name = "attachment[2]", required = false) MultipartFile attachment3,
#RequestPart(name = "attachment[3]", required = false) MultipartFile attachment4,
#RequestPart(name = "attachment[4]", required = false) MultipartFile attachment5,
#RequestPart(name = "caseDetails") CaseDetails caseDetails) {
Map<String,String> result = new HashMap<>();
result.put("success", "true");
return result;
}
}
UploadControllerTest.java:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = UploadControllerTest.TestConfig.class)
public class UploadControllerTest {
#Autowired
private UploadController uploadController;
#Test
public void testSubmitCase() throws Exception {
MockMultipartFile file1 = new MockMultipartFile("attachment[0]", "filename-1.txt", "text/plain", "1".getBytes());
MockMultipartFile file2 = new MockMultipartFile("attachment[1]", "filename-2.txt", "text/plain", "2".getBytes());
MockMultipartFile file3 = new MockMultipartFile("attachment[2]", "filename-3.txt", "text/plain", "3".getBytes());
MockMultipartFile file4 = new MockMultipartFile("attachment[3]", "filename-4.txt", "text/plain", "4".getBytes());
MockMultipartFile file5 = new MockMultipartFile("attachment[4]", "filename-5.txt", "text/plain", "5".getBytes());
MockMultipartFile caseDetailsJson = new MockMultipartFile("caseDetails", "", "application/json","{\"exampleAttr\": \"someValue\"}".getBytes());
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(uploadController).build();
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/submitCase")
.file(file1)
.file(file2)
.file(file3)
.file(file4)
.file(file5)
.file(caseDetailsJson))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.content().string("{\"success\":\"true\"}"))
.andReturn();
}
#Configuration
static class TestConfig {
#Bean
public UploadController uploadController() {
return new UploadController();
}
}
}
Please note, in UploadControllerTest the JSON data must be wrapped in a MockMultipartFile - equivalent to the uploaded files. Make sure, that jackson-core and jackson-databind are available on the classpath.

Related

Use Aws4RequestSigner to sign PAAPI 5 Request

I'm trying to use Aws4RequestSigner in a VS2015 form to sign a search request to Amazon PAAPI.
https://www.nuget.org/packages/Aws4RequestSigner/
I get this response from the API:
{"__type":"com.amazon.paapi5#IncompleteSignatureException","Errors":[{"Code":"IncompleteSignature","Message":"The request signature did not include all of the required components. If you are using an AWS SDK, requests are signed for you automatically; otherwise, go to https://webservices.amazon.com/paapi5/documentation/sending-request.html#signing."}]}
private async void Form1_Load(object sender, EventArgs e)
{
_accessKey = "x";
_secretKey = "x";
_service = "ProductAdvertisingAPIv1";
_region = "us-east-1";
_requestUri = new Uri("https://webservices.amazon.com/paapi5/searchitems");
var payload = new
{
Keywords = "Harry",
Marketplace = "www.amazon.com",
PartnerTag = "x0d-20",
PartnerType = "Associates",
Resources = new string[] { "Images.Primary.Small", "ItemInfo.Title", "Offers.Listings.Price" },
SearchIndex = "All"
};
string jsonString = JsonConvert.SerializeObject(payload);
var content = new StringContent(jsonString, Encoding.UTF8, "application/json");
var xAmzDate = GetTimeStamp();
content.Headers.Add("content-encoding", "amz-1.0");
content.Headers.Add("x-amz-date", xAmzDate);
content.Headers.Add("x-amz-target", "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.SearchItems");
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = _requestUri,
Content = content
};
request.Headers.Host = "webservices.amazon.com";
var contentType = new MediaTypeHeaderValue("application/json");
contentType.CharSet = "utf-8";
request.Content.Headers.ContentType = contentType;
var signer = new AWS4RequestSigner(_accessKey, _secretKey);
request = await signer.Sign(request, _service, _region);
try
{
var client = new HttpClient();
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
}
// response.EnsureSuccessStatusCode();
txtDisplay.Text = await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex)
{
string error = ex.Message;
txtDisplay.Text = error;
}
}
private string GetTimeStamp()
{
return DateTime.UtcNow.ToString("yyyyMMdd\\THHmmss\\Z");
}
It could be that the headers are being added incorrectly or Aws4RequestSigner is simply outdated.

How to use Moq in unit test that calls another method in same EF Repository

In my project I am using Repository.
I'm trying to unit test a SAVE method and return the call value via the Get method.
I'm having a Repository Query mockup issue when calling through the Get method. Can you help me in this case?
I Have a Class:
public class ClientRoleBo
{
private readonly IRepositoryBase<ClientRole> _repository;
public ClientRoleBo(IRepositoryBase<ClientRole> repository)
{
_repository = repository;
}
public Task<ClientRoleResp?> Get(Guid clientGuid, Guid id)
{
return Task.FromResult(
_repository.Query(x => x.Guid == id && x.ClientGuid == clientGuid && !x.IsDeleted)
.Select(x => new ClientRoleResp
{
Code = x.Code,
Guid = x.Guid,
IsActive = x.IsActive,
Name = x.Name
}).FirstOrDefault()
);
}
public async Task<ClientRole> Save(Guid clientGuid, Guid? guid, ClientRoleReq req)
{
ClientRole? data = null;
var existItem = _repository.Query(x => x.Code == req.Code && x.ClientGuid == clientGuid).FirstOrDefault();
if (existItem != null)
throw new HttpResponseException(400, "Exist clientrole");
data = new()
{
Code = req.Code,
Name = req.Name,
IsActive = req.IsActive,
ModifiedDate = DateTime.Now,
CreatedDate = DateTime.Now,
ClientGuid = clientGuid
};
await _repository.AddAsync(data);
return (await Get(clientGuid, data!.Guid))!;
}
}
I have a issue when code call method "Save" return data of method "Get" same Repository
My Mock Repository:
public class TestClientRole{
public static IRepositoryBase<TEntiy> MockRepo<TEntiy>(TEntiy[] data, Expression<Func<TEntiy, bool>> returnExpression = null) where TEntiy : class
{
var mock = new Mock<IRepositoryBase<TEntiy>>();
mock.Setup(x => x.Query(
It.IsAny<Expression<Func<TEntiy, bool>>>()
)).Returns(returnExpression != null ? data.AsQueryable().Where(returnExpression).AsEnumerable() : data.AsEnumerable());
return mock.Object;
}
[Fact]
public void Save()
{
var clientRoles = new ClientRole[]
{
new ClientRole
{
Code = "123",
Name = "Role1",
Guid = Guid.NewGuid(),
},
new ClientRole
{
Code = "1234",
Name = "Role2",
Guid = Guid.NewGuid(),
}
};
var mockRepo = MockRepo<ClientRole>(clientRoles, x => x.Guid == clientRoles[0].Guid);
var svc = new ClientRoleBo(mockRepo);
var res = svc.Save(Guid.NewGuid, null, new ClientRoleReq { Code = "Role", Name = "Role" }).GetAwaiter().GetResult();
Assert.True(res.Guid == clientRoles[0].Guid);
}
}

unit test for MediatR in .net6 with generic repo returns null repository

I am using MediatR for command query segregation.
I want to test the command method, my command method accept a clientappsetting model as an input .here you can see my whole handler and command code :
AddClientAppSettingCommandHandler : IRequestHandler<AddClientAppSettingCommand, AddClientAppSettingResponse>
{
private readonly ICurrentUserService _userService;
private readonly IRepository<ClientAppSettings> _repository;
public AddClientAppSettingCommandHandler(ICurrentUserService userService,
IRepositoryAccessor repositoryAccessor)
{
_userService = userService;
_repository = repositoryAccessor.GetRepository<ClientAppSettings>(_userService.CustomerIsin,
reThrowException: true, type: DatabaseType.Raven);
}
public async Task<AddClientAppSettingResponse> Handle(AddClientAppSettingCommand request,
CancellationToken cancellationToken)
{
var entity = new ClientAppSettings(_userService.CustomerIsin)
{
LightTheme = request.Setting.LightTheme,
Order = request.Setting.Order,
Notch = request.Setting.Notch,
PageSize = request.Setting.PageSize,
ApplyCommissionInPortfolio = request.Setting.ApplyCommissionInPortfolio,
UseClosingPriceInPortfolioTotalValue = request.Setting.UseClosingPriceInPortfolioTotalValue,
ShowNotifications = request.Setting.ShowNotifications,
NoSleep = request.Setting.NoSleep,
NoBalance = request.Setting.NoBalance,
DataTracker = request.Setting.DataTracker,
UserStatusBarToUp = request.Setting.UserStatusBarToUp,
PortfolioBasedOnLastPositivePeriod = request.Setting.PortfolioBasedOnLastPositivePeriod,
};
var cRepository = CacheableRepository<ClientAppSettings>.From(_repository);
var result = await cRepository.AddOrUpdateAsync(entity);
if (!result.IsSucceeded)
throw new EasyException(EasyException.DATABASE_EXCEPTION, result.Error);
return AddClientAppSettingResponse.Map(entity);
}
As you can see my handler has two dependencies ICurrentUserService , IRepositoryAccessor
My problem is IRepositoryAccessor when I run the test the repository object is null .
Here is my repository interface and imp ;
public interface IRepositoryAccessor
{
IRepository<TEntity> GetRepository<TEntity>(
string shard = "public",
DatabaseType type = DatabaseType.Raven,
Type inheritedRepository = null,
bool manualDisposing = false,
bool reThrowException=false) where TEntity : BaseEntity;
void CloseSession();
}
The imp :
public sealed class RepositoryAccessor : IRepositoryAccessor, IDisposable
{
private static readonly Dictionary<Type, object> FlyweightSqlGenerator = new();
private readonly List<IDisposable> _sessions = new();
private readonly ITracer _tracer;
private readonly IConfiguration _configuration;
public RepositoryAccessor(IConfiguration configuration, ITracer tracer = null)
{
_configuration = configuration;
_tracer = tracer;
}
public void CloseSession()
{
for (int i = 0; i < _sessions.Count; i++)
{
_sessions[i].Dispose();
}
_sessions.Clear();
}
public void Dispose() => CloseSession();
public IRepository<TEntity> GetRepository<TEntity>(
string shard = "public",
DatabaseType type = DatabaseType.Raven,
Type inheritedRepository = null,
bool manualDisposing = false,
bool reThrowException = false) where TEntity : BaseEntity
{
if (type == DatabaseType.Raven)
{
return GetRavenRepository<TEntity>(inheritedRepository, shard, manualDisposing, reThrowException);
}
else if (type == DatabaseType.Redis)
{
return new RedisRepository<TEntity>();
}
return GetSQLRepository<TEntity>(inheritedRepository, manualDisposing, reThrowException);
}
}
And here is my test :
[Fact]
public async void Test1()
{
//Arange
var mediator = new Mock<IMediator>();
var userservice = new Mock<ICurrentUserService>();
var repo = new Mock<IRepositoryAccessor>();
AddClientAppSettingCommand command = new AddClientAppSettingCommand(new domain.Entities.ClientAppSettings());
AddClientAppSettingCommandHandler handler = new AddClientAppSettingCommandHandler(userservice.Object,repo.Object);
//Act
var x = await handler.Handle(command, new System.Threading.CancellationToken());
}
And when I run the test with debug mode my repository is null :
I should define IAccessor and IRepository as a mock and setting up the Irepository for IAccessor as you can see :
var repoacc = new Mock<IRepositoryAccessor>();
var repo = new Mock<domain.Interfaces.IRepository<ClientAppSettings>>();
repoacc.Setup(i => i.GetRepository<ClientAppSettings>(It.IsAny<string>(), DatabaseType.Raven, null, false, true)).Returns(repo.Object);

xUnit ClaimsPrincipal mock and passing into controller User is null

I have been reading articles trying to figure this one out. Structured off of this article How to add claims in a mock ClaimsPrincipal. I am still getting a null user from my controller and test fails on a null object of User inside the controller.
BusinessController
[Route("api/[controller]")]
[ApiController]
public class BusinessController : ControllerBase
{
private readonly IGBusinessRepository businessRepository;
private readonly IPersonRepository personRepository;
private readonly IUserClaims userClaims;
public BusinessController(IGBusinessRepository businessRepository,
IPersonRepository personRepository,
IUserClaims userClaims)
{
this.businessRepository = businessRepository;
this.personRepository = personRepository;
this.userClaims = userClaims;
}
// GET api/<BusinessController>/5
[HttpGet("{id}")]
[Authorize]
public async Task<IActionResult> GetBusiness(Guid businessId)
{
var userGuid = userClaims.GetUserGuid(User.Claims);
var ownerId = await personRepository.GetPersonIdByGUID(userGuid);
var business = await businessRepository.GetBusinessById(businessId);
if(business != null && business.OwnerId == businessId)
{
return Ok(business);
}
return BadRequest("Bad business id or your not the owner");
}
UserClaims
public class UserClaims : IUserClaims
{
public string GetUserGuid(IEnumerable<Claim> claims)
{
var claimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
var guidClaim = claims.Where(c => c.Type == claimType).Select(s => s.Value).SingleOrDefault();
return guidClaim;
}
}
TestIdentity
public class TestIdentity : ClaimsIdentity
{
public TestIdentity(params Claim[] claims) : base(claims)
{
}
}
TestPrincipal
public class TestPrincipal : ClaimsPrincipal
{
public TestPrincipal(params Claim[] claims) : base(new TestIdentity(claims))
{
}
}
BusinessControllerTests
public class BusinessControllerTests
{
//Controller
private readonly Mock<IGBusinessRepository> mockBusinessRepository;
private readonly Mock<IPersonRepository> mockPersonRepository;
private readonly Mock<IUserClaims> mockUserClaims;
private BusinessController controller;
//Objects
private Guid id = Guid.NewGuid();
public BusinessControllerTests()
{
mockBusinessRepository = new Mock<IGBusinessRepository>();
mockPersonRepository = new Mock<IPersonRepository>();
mockUserClaims = new Mock<IUserClaims>();
controller = new BusinessController(mockBusinessRepository.Object, mockPersonRepository.Object, mockUserClaims.Object);
}
[Fact]
public async Task GetBussiness_NotBusinessOwner_ReturnsBadRequest()
{
//Arrange
var userGuidString = Guid.NewGuid().ToString();
var ownerId = Guid.NewGuid();
var userClaim = new TestPrincipal(new Claim("name", "user#domain.com"));
Thread.CurrentPrincipal = userClaim;
//mockUserClaims.Setup(repo => repo.GetUserGuid(userClaim)).Returns(userGuidString);
mockPersonRepository.Setup(repo => repo.GetPersonIdByGUID(userGuidString));
mockBusinessRepository.Setup(repo => repo.GetBusinessById(id)).ReturnsAsync(business);
//Act
var result = await controller.GetBusiness(id);
//Assert
Assert.IsType<BadRequestResult>(result);
}
private GobiezBusiness business = new GobiezBusiness()
{
Id = new MongoDB.Bson.ObjectId(),
BusinessId = Guid.NewGuid(),
Name = "Test",
Email = "Test#helpme.com",
Address = "123 A street",
State = "WA",
ZipCode = "12345",
PhoneNumber = "123-456-7890",
OwnerId = Guid.NewGuid()
};
}
The controller was not arranged correctly to be able to access the principal
// ... omitted for brevity
var userClaim = new TestPrincipal(new Claim("name", "user#domain.com"));
var httpContext = new DefaultHttpContext() {
User = userClaim;
};
//Controller needs a controller context to access HttpContext
var controllerContext = new ControllerContext() {
HttpContext = httpContext
};
//assign context to controller
BusinessController controller = new BusinessController(
mockBusinessRepository.Object,
mockPersonRepository.Object,
mockUserClaims.Object)
{
ControllerContext = controllerContext
};
//Act
var result = await controller.GetBusiness(id);
// ... omitted for brevity
The controller should also be created within the scope of the test being executed.
This line in the subject under test
//...
var userGuid = userClaims.GetUserGuid(User.Claims);
//...
Should now return the created TestPrincipal arranged in the test

Spring MVC File Upload with multipart data and unit test

I am using Spring 4 latest, and I generally have no problem writing RESTful controllers. There is a legacy web-app, which is using java.net.HTTPUrlConnection to do a multi-part upload. There are 3 pieces of data we are uploading:
1 is a PDF file, and we have the bytes, then the other two pieces of data are just 2 string fields.
First let me show you the Spring REST controller to accept the data:
#RequestMapping(value = "/save", method = RequestMethod.POST, produces = "application/json", consumes = "multipart/form-data")
public #ResponseBody boolean saveData(#RequestPart(value = "field1") String field1, #RequestPart(value = "field2") String field2, #RequestParam(value = "pdfbytes") String pdfbytes)
{
System.out.println("saveData: field1=" + field1);
System.out.println("saveData: field2=" + field2);
System.out.println("saveData: pdfbytes=" + pdfbytes);
boolean response = true;
return response;
}
The code in front-end, for sending the data using 'java.net.HttpURLConnection'
looks like this:
String boundary = MultiPartFormOutputStream.createBoundary();
URL uploadDocumentUrl = new URL(protocol + "://" + host + UPLOAD_EDITED_DOCUMENT);
HttpURLConnection urlConn = (HttpURLConnection) MultiPartFormOutputStream.createConnection(uploadDocumentUrl);
urlConn.setRequestProperty("Content-Type", MultiPartFormOutputStream.getContentType(boundary));
urlConn.setRequestProperty("Connection", "Keep-Alive");
urlConn.setRequestProperty("Cache-Control", "no-cache");
urlConn.setRequestProperty("User-Agent", userAgent);
urlConn.setRequestMethod("POST");
MultiPartFormOutputStream out = new MultiPartFormOutputStream(urlConn.getOutputStream(), boundary);
String pdfbytes= getEncodedDocument(pdf);
out.writeField("field1", field1);
out.writeField("field2", field2);
out.writeField("pdfbytes", pdfbytes);
out.close();
int responseCode = urlConn.getResponseCode();
String responseMessage = urlConn.getResponseMessage();
"MultiPartFormOutputStream" is a custom object that was created to send data via HttpUrlConnection, it's pretty standard code. I do trust it at this time.
So, based on how we are sending the data, do I need to change the Controller to do anything different, or does that look ok?
Now here is the code, that I am using to Unit Test that controller:
#Test
public void testMockUpload() throws Exception
{
// Load resource being uploaded
byte[] pdfbytes = getByteArrayFromFile(FILENAME);
MockMultipartFile firstFile = new MockMultipartFile("field1", "", "text/plain", "field1 data".getBytes());
MockMultipartFile secondFile = new MockMultipartFile("field2", "", "text/plain", "field2 data".getBytes());
MockMultipartFile jsonFile = new MockMultipartFile("pdfbytes", "", "text/plain", pdfbytes);
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.fileUpload(BASE_URL + "/save").file(firstFile).file(secondFile).file(jsonFile)
.with(user(USERNAME).roles("role1", "role2")).contentType(MediaType.MULTIPART_FORM_DATA_VALUE);
this.mockMvc.perform(requestBuilder).andDo(print()).andExpect(status().isOk());
}
And the error I get back now, is:
org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException
which I am looking into. If I need to make any changes on how I need to create my test, I am very open to that. Eventually, I will get everything to sync up between the sending code, the receiving controller, and the unit test.
Thanks in advance! As usual, if there is any other data, or information, I can provide, please let me know. Thanks!
To upload one file you would define the RequestParam type as org.springframework.web.multipart.MultipartFile;
#RequestMapping(value = "/save", method = RequestMethod.POST, produces = "application/json", consumes = "multipart/form-data")
public #ResponseBody boolean saveData(#RequestParam(value = "file") MultipartFile file)
{
return response;
}
For Multiple files I'd try creating a Wrapper form:
public class UploadForm{
private List<MultipartFile> files;
}
Bind to this in the controller:
#RequestMapping(value = "/save", method = RequestMethod.POST, produces = "application/json", consumes = "multipart/form-data")
public #ResponseBody boolean saveData(#ModelAttribute uploadForm)
{
return response;
}
and then use Spring's support for indexed fields to bind to a collection:
Test:
MockMultipartFile firstFile = new MockMultipartFile("files[0]", "", "text/plain", "field1 data".getBytes());
MockMultipartFile secondFile = new MockMultipartFile("files[1]", "", "text/plain", "field2 data".getBytes());
MockMultipartFile jsonFile = new MockMultipartFile("files[2]", "", "text/plain", pdfbytes);
Client:
out.writeField("files[0]", file1Bytes);
out.writeField("files[1]", file2Bytes);
...